Skip to content

A payout is always in one of five statuses, and your ledger should model the same five. Read it at GET /payments/organizations/{orgId}/orders/{payoutId} or from its payout.* webhook, and accept any forward transition from the status you hold.

Status Meaning Your balance Terminal?
pending Accepted, not yet sent to the payment network Held No
processing Handed to the payment network Committed No
completed The recipient’s bank paid them Sent Almost (see the bank return below)
failed Not paid, or returned. See failureCode Credited back as a separate entry when fundsReturned is true Yes
canceled Stopped before sending; only possible while waiting on your own funding Never left. fundsReturned: true Yes

A payout can make only these transitions:

pendingReserved, not sentprocessingDispatched, committedcompletedRecipient creditedfailedInspect failureCodecanceledFunds returnedInstant rails skip processingreturned_by_bankdays later
Every permitted transition. completed can still become failed when the destination bank returns the funds days later.

Not every payout passes through every status. A rail that settles on acceptance goes from pending straight to completed, and a status entered and left between two reads is never reported. In the sandbox, every payout is processing for about 8 seconds; suffix 0002 holds it for about 50.

On failed or canceled, fundsReturned: true means the money is back in your balance (for canceled, it never left). Absent means it is not confirmed back, so don’t re-credit anyone yet. Read the flag, not the code: a compliance_rejected payout has no fundsReturned while the funds are held for review. In GET /balance_transactions, a payout debit with no matching payout_return row means the money did not come back.

A failed payout carries a failureCode. It is a payout state, not an error response.

failureCode What happened Is the money back? What to do
returned_by_bank It settled, then the receiving bank returned it Yes (fundsReturned: true) Tell your user. Reverse whatever you credited
account_invalid The account details are wrong Check fundsReturned Ask for correct details, create a new recipient
account_cannot_receive The account cannot accept this payment Check fundsReturned Try another account or corridor
compliance_rejected Refused by compliance screening Not automatically Contact us with the payoutId. Do not retry
limit_exceeded Above a corridor or account limit Check fundsReturned Split it, or check limits on the corridors call
quote_expired Too long between quoting and sending Check fundsReturned Re-quote and send again
authorization_not_completed An authorization step was not finished Check fundsReturned Start again
execution_failed It did not go through, cause not established Check fundsReturned Retry only once the money is back (below)

“Check fundsReturned” means exactly that: on some routings these codes arrive without the flag while the funds are still unconfirmed. The money is back only when the payout carries fundsReturned: true, or when GET /balance_transactions shows a payout_return row for it. Until then, a retry under a new idempotency key debits your balance a second time while the first debit may still be held.

The enum also lists insufficient_funds and unknown, which no payout carries today: a short balance is refused up front with 400 INSUFFICIENT_BALANCE, and a failure with no specific cause is reported as execution_failed. New codes arrive without a major version, so treat an unrecognized one as execution_failed.

The sandbox reaches only account_invalid (0001), compliance_rejected (0004) and returned_by_bank (0003). The rest, and stage, come from live rails only, so write a branch for every code. Errors that refuse a request before a payout exists are on Errors.

A batch has its own statuses. They track creation, not settlement:

received → validating → (awaiting_confirmation | creating) → completed
canceled · failed (terminal)

completed means every line became a payout or was refused; counts says which. failed means the run itself broke. Each created line carries a payoutId, and that payout follows the five statuses above.

With M-of-N approval on, a POST /payouts or batch confirm above the threshold answers 202 with an approval instead of a payout. While it is open there is no payoutId and nothing in GET /orders. When it executes, the payout starts at pending.

pending → approved → executing → executed (the payout now exists; payoutId on the approval)
pending → rejected · expired (terminal; nothing was created)
executing → execution_failed · execution_unknown (terminal; nothing was created, or resolved by hand)

Read approvals at GET …/payouts/approvals and …/approvals/{approvalId}. Every transition except into executing and execution_unknown is also a payout_approval.* event, and payout_approval.executed carries the payoutId. An owner or admin approves or rejects in the dashboard, and a pending approval expires after 24 hours.

Some rails add stage: awaiting_details or under_review. (The enum also lists settling, which no payout carries today.) It answers “where is my payment?” for support; don’t drive your ledger from it.

Was this page helpful?