Transaction lifecycle
The five states every transaction moves through, what moves it, and how to reason about gateway-specific statuses.
Every deposit and payout is a transaction with a reference (txn_…) and a state. OmiCloud maps every gateway's own status vocabulary onto five canonical states, so your code handles one model whichever gateway executed the money movement.
The five states
submitted ──▶ processing ──▶ settled ──▶ reversed
│
└──────▶ declined
| State | Meaning | Terminal |
|---|---|---|
submitted | OmiCloud created the transaction; no gateway has accepted it yet. You will rarely observe this — it lasts milliseconds. | no |
processing | The gateway accepted it. For a deposit: waiting for the end user to pay and for confirmation. For a payout: the gateway is sending. | no |
settled | Funds arrived. For crypto, crypto.txHash is set. | yes, except → reversed |
declined | It failed. declineReason says why. | yes |
reversed | A settled transaction was undone by the gateway (chargeback, clawback). Rare; treat as a negative adjustment. | yes |
These are the only legal transitions. OmiCloud rejects anything else — a gateway callback that would move declined to settled, for example, is logged and ignored rather than applied. That protects you from out-of-order or replayed gateway callbacks.
gatewayState and declineReason
gatewayState and declineReasonAlongside state, a transaction carries:
gatewayState— the gateway's own status word, verbatim (for DCE:confirmed,failed,PENDING_APPROVAL…). It is there for support conversations and debugging. Do not branch on it; it differs per gateway and can change when a gateway changes its API.declineReason— a stable, snake_case code set whenstateisdeclined:
declineReason | Meaning |
|---|---|
session_expired | The hosted checkout expired before a deposit arrived. |
gateway_rejected | The gateway refused or failed the transaction. gatewayState has its word for it. |
gateway_unreachable | OmiCloud could not reach the gateway when submitting. Safe to retry with a new request. |
insufficient_funds | Payouts: the gateway account could not cover it. OmiCloud is alerted; retry later. |
invalid_destination | Payouts: the gateway rejected the destination address. |
amount_out_of_range | The gateway's limits were tighter than advertised at the time. |
New reasons can be added. Treat an unrecognised reason as a generic decline.
The event log
GET /v1/transactions/{reference} returns events — every transition, oldest first, with its source:
source | Who moved it |
|---|---|
operator_submission | Your API call created it (→ submitted). |
system | OmiCloud itself: gateway accepted (→ processing), session expiry sweep (→ declined), cascading retry. |
webhook | The gateway's callback (→ settled, → declined, → reversed). |
The log is append-only and is what OmiCloud reconciles against. If you ever disagree with a state, the log is the first thing support will look at.
How this maps to what you do
| You want to | Look at |
|---|---|
| Credit the end user's balance | state == settled — and only then. Use netAmount if you pass fees through, amount if you absorb them. |
| Show "pending" in your UI | state == processing |
| Tell the end user it failed | state == declined, message by declineReason |
| Debit a previously credited balance | state == reversed |
| Match to your own records | operatorReference (yours) or reference (ours) — both are on every event and every read |
Deposits vs payouts
The state machine is identical for both directions. The differences are where the money comes from and what processing is waiting on:
| Deposit | Payout | |
|---|---|---|
| Created by | POST /v1/sessions | POST /v1/payouts |
processing waits for | the end user to pay on the hosted page, then chain/gateway confirmation | the gateway to send and confirm |
| Can expire | yes → declined / session_expired | no |
crypto.address | the deposit address shown to the end user | the destination you supplied |
Updated about 2 months ago