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"
}
}| Field | Meaning |
|---|---|
id | Unique per event. Deduplicate on it — the same event can be delivered more than once. |
type | The event type, below. |
createdAt | When the state change happened. Use it to discard stale events. |
version | The API version the payload follows. |
endpointId | Which of your endpoints this was sent to. |
data.transaction | The reference to fetch. |
data.operatorReference | Your id, so you can match without a lookup. |
data.state / data.previousState | The 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.
| Type | Fires when | data extras |
|---|---|---|
transaction.processing | A gateway accepted the transaction. For sessions this fires at creation; you already have the response, so most receivers ignore it. | — |
transaction.settled | Funds arrived. The one you act on. | — |
transaction.declined | Terminal failure. | declineReason |
transaction.reversed | A settled transaction was undone by the gateway. | — |
session.expired | A hosted checkout hit expiresAt with no deposit. The transaction is declined with declineReason: session_expired; this event fires instead of transaction.declined. | session |
webhook.test | You 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:
- Verify before you parse. Never touch JSON from an unverified body.
- Respond fast. OmiCloud waits 10 seconds. Anything slower is a retry, and slow handlers plus retries is how receivers melt. Acknowledge, then work.
- Deduplicate on
id. Delivery is at-least-once. - 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.
Updated about 2 months ago