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
StateMeaningTerminal
submittedOmiCloud created the transaction; no gateway has accepted it yet. You will rarely observe this — it lasts milliseconds.no
processingThe gateway accepted it. For a deposit: waiting for the end user to pay and for confirmation. For a payout: the gateway is sending.no
settledFunds arrived. For crypto, crypto.txHash is set.yes, except → reversed
declinedIt failed. declineReason says why.yes
reversedA 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

Alongside 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 when state is declined:
declineReasonMeaning
session_expiredThe hosted checkout expired before a deposit arrived.
gateway_rejectedThe gateway refused or failed the transaction. gatewayState has its word for it.
gateway_unreachableOmiCloud could not reach the gateway when submitting. Safe to retry with a new request.
insufficient_fundsPayouts: the gateway account could not cover it. OmiCloud is alerted; retry later.
invalid_destinationPayouts: the gateway rejected the destination address.
amount_out_of_rangeThe 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:

sourceWho moved it
operator_submissionYour API call created it (→ submitted).
systemOmiCloud itself: gateway accepted (→ processing), session expiry sweep (→ declined), cascading retry.
webhookThe 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 toLook at
Credit the end user's balancestate == settled — and only then. Use netAmount if you pass fees through, amount if you absorb them.
Show "pending" in your UIstate == processing
Tell the end user it failedstate == declined, message by declineReason
Debit a previously credited balancestate == reversed
Match to your own recordsoperatorReference (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:

DepositPayout
Created byPOST /v1/sessionsPOST /v1/payouts
processing waits forthe end user to pay on the hosted page, then chain/gateway confirmationthe gateway to send and confirm
Can expireyes → declined / session_expiredno
crypto.addressthe deposit address shown to the end userthe destination you supplied

Did this page help you?