Getting started

Quickstart

Run Sentinel locally, make a key, send events, and work the alert they raise.

This takes about 15 minutes. You'll run Sentinel on your own machine, send a few transactions, get a decision back, and close the alert they raise with a second key.

There's no hosted sandbox yet, so you run your own copy.

1. Start Sentinel

You need Go (the version in go.mod), Docker with Compose, and make. From the root of the Sentinel repository:

Terminal
cp .env.example .env   # local settings: PostgreSQL on 5433, Redis on 6379
make up                # starts PostgreSQL, Redis and Redpanda
make run               # applies the migrations, then serves on :8080

Check that it's up:

Terminal
curl localhost:8080/healthz
Response · 200
{
  "status": "ok",
  "version": "dev"
}

2. Make a key

In another terminal, create an API key. The admin command needs SENTINEL_DATABASE_URL, so load .env into the shell first. The key is printed once, so keep it.

Terminal
set -a; . ./.env; set +a
go run ./cmd/sentinel-admin key create -name quickstart

Put the base URL and key in your shell so the examples work as written:

Terminal
export SENTINEL_URL=http://localhost:8080
export SENTINEL_KEY=snt_live_...

3. Create a customer

Tell Sentinel who the sender is. IDs are yours, so use the ones your own systems already use.

Terminal
curl -X POST "$SENTINEL_URL/v1/entities" \
  -H "Authorization: Bearer $SENTINEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "cus_10442",
    "kind": "customer",
    "attributes": {
      "name": "Test Customer",
      "kyc_result": "verified"
    }
  }'

4. Send an event

Send a transfer in sync mode, so the decision comes back on the same request.

Terminal
curl -X POST "$SENTINEL_URL/v1/events?mode=sync" \
  -H "Authorization: Bearer $SENTINEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "transfer.outbound",
    "idempotency_key": "txn_quickstart_001",
    "occurred_at": "2026-10-15T11:02:41Z",
    "amount": {
      "value": 4950,
      "currency": "USD"
    },
    "rail": "mobile_money",
    "source": {
      "entity_id": "cus_10442",
      "account_id": "wal_8812"
    },
    "destination": {
      "account_id": "wal_6603"
    }
  }'

A first transfer from a new customer comes back as allow with no reasons:

Response · 200
{
  "id": "5f0c2b7e-3a41-4c1d-9b6e-1f2a3b4c5d6e",
  "event_id": "9b21d4c8-7e5f-4a3b-8c2d-6e7f8a9b0c1d",
  "decision": "allow",
  "risk_score": 0,
  "reasons": [],
  "created_at": "2026-10-15T11:02:41.041Z"
}

5. Trip a rule

The built-in rule structuring_below_threshold fires on the third transfer from one source in 24 hours with an amount from 4,500 to just under 5,000. Send the same request twice more, with a new idempotency_key each time (txn_quickstart_002, then txn_quickstart_003). The third comes back as a review:

Response · 200
{
  "id": "a81e4f02-6c9d-4b7a-9e3f-2d1c0b9a8f7e",
  "event_id": "c4d5e6f7-0a1b-4c2d-8e9f-3a4b5c6d7e8f",
  "decision": "review",
  "risk_score": 60,
  "alert_id": "c7d4e1a2-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
  "reasons": [
    {
      "rule": "structuring_below_threshold",
      "version": 1,
      "detail": "repeated transfers just below the reporting threshold within 24h"
    }
  ],
  "created_at": "2026-10-15T11:09:12.318Z"
}

A score of 60 is a review, so the decision went on an alert, and alert_id points to it.

Resending an idempotency_key returns the original event and decision. Retries never create duplicates, so use a new key for each new transaction.

6. See the alert

Terminal
curl "$SENTINEL_URL/v1/alerts?status=open" \
  -H "Authorization: Bearer $SENTINEL_KEY"

The alert comes back with the decision and event behind it. Send a fourth transfer and list again: it joins the same alert, and occurrences goes from 1 to 2. Alerts are grouped by source, so one busy customer doesn't fill the queue.

Response · 200
{
  "alerts": [
    {
      "id": "c7d4e1a2-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
      "decision_id": "a81e4f02-6c9d-4b7a-9e3f-2d1c0b9a8f7e",
      "event_id": "c4d5e6f7-0a1b-4c2d-8e9f-3a4b5c6d7e8f",
      "status": "open",
      "priority": "medium",
      "occurrences": 2,
      "last_event_at": "2026-10-15T11:02:41Z",
      "created_at": "2026-10-15T11:09:12.318Z",
      "updated_at": "2026-10-15T11:12:30.904Z",
      "decision": "review",
      "risk_score": 60,
      "reasons": [
        {
          "rule": "structuring_below_threshold",
          "version": 1,
          "detail": "repeated transfers just below the reporting threshold within 24h"
        }
      ],
      "event": {
        "type": "transfer.outbound",
        "occurred_at": "2026-10-15T11:02:41Z",
        "amount": {
          "value": 4950,
          "currency": "USD"
        },
        "rail": "mobile_money",
        "source_id": "cus_10442"
      }
    }
  ]
}

7. Close it with a second key

Closing an alert takes two people. Ask to close it as a false positive:

Terminal
curl -X POST "$SENTINEL_URL/v1/alerts/c7d4e1a2-5b6c-4d7e-8f9a-0b1c2d3e4f5a/closure" \
  -H "Authorization: Bearer $SENTINEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "disposition": "false_positive"
  }'

Approving with the same key returns 403. Make a second key for a reviewer, this time through the API:

Terminal
curl -X POST "$SENTINEL_URL/v1/keys" \
  -H "Authorization: Bearer $SENTINEL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "reviewer"
  }'

Copy the secret from the response into SENTINEL_REVIEWER_KEY, then approve with it:

Terminal
curl -X POST "$SENTINEL_URL/v1/alerts/c7d4e1a2-5b6c-4d7e-8f9a-0b1c2d3e4f5a/closure/approve" \
  -H "Authorization: Bearer $SENTINEL_REVIEWER_KEY"

The alert is now closed, with closure_requested_by and closed_by showing the two keys.

That's the whole loop: an event goes in, a decision comes out, an alert waits for a person, and two people agree on how it ends.

See it in the dashboard

The dashboard has the Overview, the alert queue with notes and closure, screening and match review, rules with backtests, and the API keys screen. To run it in development:

Terminal
cd web
npm install
npm run dev   # http://localhost:5173, proxies /v1 to the API on :8080

Sign in with an API key. To serve the dashboard from the API itself, build with make bundle and run ./bin/sentinel-api. See Installation.

To see a first alert without curl, use Send a test event. It's on the empty Alerts screen and under Developers. Pick a scenario, such as structuring under the threshold or a sanctions name match, and the dialog sends it and shows the decision or the screening result. When a test transfer raises an alert, the dialog links to it. There's no sandbox, so these are real events that run through your live rules, and their alerts join the queue. Close them as false positives once you've looked.

Next steps

Sentinel is a Somu Labs product. © 2026 Somu Labs Ltd.