Concepts

Events and decisions

What happens between sending an event and getting allow, review or block back.

Send an event, get a decision. In sync mode the answer comes back on the same request, fast enough to wait for before you move money.

Events

An event is something that happened to a customer: a transfer, a card authorisation, a cash-in or cash-out at an agent, a payout, a login, a change of phone number.

Events are immutable. Each one carries an idempotency_key that's unique within your organisation, so a retried request returns the original event and decision instead of creating a second one.

FieldRequiredMeaning
typeYesWhat happened, for example transfer.outbound, card.authorization, cash_out, login
idempotency_keyYesYour unique ID for this event. Replays return the original result.
occurred_atYesWhen it happened on your side, RFC 3339
amountFor money eventsvalue as a number and currency as a three-letter ISO 4217 code
railNoWhich network the money moves on, such as mobile_money, card or bank_transfer. Rules can use it.
sourceNoentity_id and account_id of the sender. Features and alert grouping use entity_id.
destinationNoWho and which account received it
deviceNoDevice details. Stored with the event, not used by rules yet.
Event
{
  "type": "transfer.outbound",
  "idempotency_key": "txn_8f2a91",
  "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"
  },
  "device": {
    "id": "dev_a3f19c"
  }
}

The request path

Here is what happens on a synchronous request, POST /v1/events?mode=sync:

StepWhereWhat happens
1Your payment service → APIYou send the event
2APIChecks the key and finds your organisation
3PostgreSQLInserts the event; a repeated idempotency key returns the stored one
4Redis or PostgreSQLReads 24-hour features for the source
5Rules engineEvaluates every active rule
6Rules engineAdds up the score and picks the decision and reasons
7PostgreSQLWrites the decision, with its reasons and feature values, and puts a review or block on an alert
8API → your payment serviceReturns 200 with the decision

Steps 3 to 7 hit the database a fixed number of times, whatever the size of the customer's history. The target is a p99 under 300 ms. With SENTINEL_REDIS_ADDR set, the counters come from Redis, and Sentinel falls back to PostgreSQL if Redis is slow or down.

The decision

Every rule that matches adds its weight to a risk score from 0 to 100 and records a reason. The score becomes one of three decisions:

ScoreDecisionWhat your system should do
Under 50allowLet the payment through
50 to 89reviewLet it through or hold it (you choose). The decision goes on an alert for an analyst.
90 and aboveblockStop the payment. The decision goes on an alert.

The score is capped at 100. The thresholds are fixed at 50 and 90 in the current release.

Take a transfer of 4,950 where both built-in rules fire:

RuleWhy it firedPoints
structuring_below_thresholdThird transfer just under 5,000 in 24 hours+60
high_velocity_24h20th transaction from this source in 24 hours+30

The total is 90, so the answer is block. Reasons are sorted by rule key:

Decision
{
  "id": "5f0c2b7e-3a41-4c1d-9b6e-1f2a3b4c5d6e",
  "event_id": "9b21d4c8-7e5f-4a3b-8c2d-6e7f8a9b0c1d",
  "decision": "block",
  "risk_score": 90,
  "alert_id": "c7d4e1a2-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
  "reasons": [
    {
      "rule": "high_velocity_24h",
      "version": 1,
      "detail": "20 or more transactions from the same source within 24h"
    },
    {
      "rule": "structuring_below_threshold",
      "version": 1,
      "detail": "repeated transfers just below the reporting threshold within 24h"
    }
  ],
  "created_at": "2026-10-15T11:02:41.041Z"
}

alert_id is set only when the decision is review or block. While a source already has an open alert, its new review and block decisions join that alert, so alert_id can point to an alert raised earlier. See Alerts.

Your payment service blocks the transfer and shows the customer a generic error. Sentinel never moves money itself.

Sync or async

ModeBehaviourUse when
syncScores before responding. Returns 200 with the decision.You need the answer before moving money
async (default)Stores the event and returns 202 with its ID. Nothing scores it yet.Loading history, so velocity features count it
Async scoring isn't built yet. An event sent in async mode is stored and counts toward later events' features, but it never gets a decision or raises an alert. Use sync for anything you want monitored.

If Sentinel can't be reached

Decide your fallback before go-live. Most teams let low-value payments through and send the event again with mode=sync once Sentinel is back, and hold high-value ones. This is called failing open or failing closed, and you can choose per amount band.

Sentinel stores every event it accepts, so nothing goes missing from the record.

Where events come from

Today your engineers call the API directly, as above. Later, connectors will pull events from a rail for you, such as a mobile money operator's API, a bank switch or a card processor. They aren't built yet.

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