Webhook events

Every event type, the envelope, and how to build a receiver that survives retries and reordering.

Webhooks are how OmiCloud tells you a transaction changed state. Instead of polling, you register an HTTPS endpoint and OmiCloud POSTs a signed event to it whenever something happens. Use the event as a trigger to fetch GET /v1/transactions/{reference} — the event body is deliberately thin.

Setting up

Create an endpoint with the API (POST /v1/webhook-endpoints) or in the console under Webhooks. You get a signing secret once. Subscribe to specific events or leave the list empty for everything.

You can have several endpoints — one per service, one per environment — each with its own secret and event filter.

The envelope

Every delivery has the same shape:

{
  "id": "evt_01j6ybq7m2vx",
  "type": "transaction.settled",
  "createdAt": "2026-09-02T10:18:41Z",
  "version": "2026-09-01",
  "operatorId": "op_ominari",
  "endpointId": "whe_01j6ya2c8r5t",
  "data": {
    "transaction": "txn_8f3a1c9e02b4",
    "operatorReference": "dep-20260902-001",
    "state": "settled",
    "previousState": "processing",
    "gateway": "dce",
    "direction": "deposit",
    "amount": "100",
    "currency": "USDT"
  }
}
FieldMeaning
idUnique per event. Deduplicate on it — the same event can be delivered more than once.
typeThe event type, below.
createdAtWhen the state change happened. Use it to discard stale events.
versionThe API version the payload follows.
endpointIdWhich of your endpoints this was sent to.
data.transactionThe reference to fetch.
data.operatorReferenceYour id, so you can match without a lookup.
data.state / data.previousStateThe transition that fired this event.

Headers on every delivery: Omi-Signature, Omi-Signature-Timestamp, Omi-Event-Id, and on retries Omi-Retry-Count / Omi-Retry-Reason. See Webhook authentication.

Event types

Events are one-to-one with state transitions, so the transaction lifecycle is the complete list of what can happen.

TypeFires whendata extras
transaction.processingA gateway accepted the transaction. For sessions this fires at creation; you already have the response, so most receivers ignore it.—
transaction.settledFunds arrived. The one you act on.—
transaction.declinedTerminal failure.declineReason
transaction.reversedA settled transaction was undone by the gateway.—
session.expiredA hosted checkout hit expiresAt with no deposit. The transaction is declined with declineReason: session_expired; this event fires instead of transaction.declined.session
webhook.testYou called POST /v1/webhook-endpoints/{id}/test. data is {}.—

GET /v1/webhook-events returns this list, so a receiver can validate its subscription at startup.

Building a receiver

on POST:
  raw = read body bytes            # before parsing — the signature covers the raw bytes
  verify Omi-Signature over (Omi-Signature-Timestamp, your URL, raw)   → 401 if bad
  reject if Omi-Signature-Timestamp older than 5 minutes               → 401
  event = parse(raw)
  if event.id already seen: respond 200                                # duplicate
  enqueue event; respond 200                                           # do the work off the request thread

Then, off the request path:

tx = GET /v1/transactions/{event.data.transaction}
if tx.state != event.data.state: the event is stale — act on tx.state, not the event
apply tx.state to your records, keyed by tx.reference
mark event.id seen

Four rules this encodes:

  1. Verify before you parse. Never touch JSON from an unverified body.
  2. Respond fast. OmiCloud waits 10 seconds. Anything slower is a retry, and slow handlers plus retries is how receivers melt. Acknowledge, then work.
  3. Deduplicate on id. Delivery is at-least-once.
  4. Fetch, don't trust. Events can arrive out of order. The transaction is the truth; the event just tells you to look.

Ordering

Not guaranteed. A transaction.settled can arrive before the transaction.processing for the same transaction if the first delivery failed and was retried. Because you fetch the transaction and act on its current state, ordering does not matter to a correct receiver.

Responding

Any 2xx acknowledges the event. Anything else — 3xx, 4xx, 5xx, a timeout, a connection failure — is retried. See Webhook retries. Do not return 4xx for "I don't care about this event"; return 200 and drop it, or narrow the endpoint's events filter.

Seeing what was sent

GET /v1/webhook-endpoints/{id}/deliveries lists every event OmiCloud tried to send to an endpoint with each attempt's response status and latency. The console shows the same view. When a receiver is misbehaving, this is where to look first.


Did this page help you?