status and operationalState, with values on Status codes; it fails in one of six ways.
A payer never pays: let it expire
Detect.status becomes expired and payment.expired fires. The deadline is expiresAt, set from expiresInSeconds at creation: 60 to 86400 seconds, defaulting to 600.
Decide. Expiry is terminal. The payment does not reopen, and funds arriving afterwards do not revive it.
Act. Stop polling. If the payer wants another attempt, create a new payment with a new idempotency key. To close the window early, cancel before any funds are detected.
reasonCode is customer_request, duplicate, abandoned or other. Cancel works only before provider detection or verified funds, and replaying it on an already-cancelled payment returns the same resource. Set expiresInSeconds to how long you will genuinely hold the obligation: a long window produces more late receipts, and a late receipt is worked by a person rather than your code.
A quote expires: refresh the option or start again
Detect.payment_quote_expired, HTTP 409.
Decide. The quote lapsed. Read the Payment to establish its current state and reconcile any funds already sent.
Act. Refresh a checkout option on the existing Payment only when it is eligible and no payment evidence has been observed. An external payout quote cannot be refreshed on the same Payment. Start a new attempt with a new key only after the original is terminal and any funds sent have been reconciled. Never replace a processing or uncertain payout. See Conversion.
A method becomes unavailable: re-read and present what is left
Detect.payment_method_not_supported (422) for this destination, chain or asset. payment_amount_below_minimum (422) when a route’s floor moves above the amount. payment_not_open (409) when the payment no longer accepts options or funds.
Decide. Availability is decided per app, environment, market and destination, at runtime. A method you read an hour ago is not a method you hold now.
Act. Re-read GET /v2/partners/config and the payment itself, then present what is left; if nothing is left for that destination, say so. None of these three codes is retryable: the identical request gets the identical refusal. View supported regions and currencies →
A provider is down: retry with the same key
Detect.payment_provider_unavailable and provider_unavailable return 503. payment_provider_error and provider_failure return 502. A timeout with no response is the same class of problem.
Decide. An error or a timeout tells you nothing about whether the operation happened.
Act. Retry with backoff, reusing the same Idempotency-Key and a byte-identical body. If the first attempt landed, you get that result back instead of a second movement. Then read the payment before acting. If the retry returns request_in_progress (409), the first request is still running: wait and read rather than retrying harder. See Idempotency.
A transfer is reverted: reverse your credit
Detect.payment.settlement_returned for a settlement that came back after delivery. deposit.reversed for a settled deposit whose accounting record was invalidated. On the payment, operationalReasonCode carries settlement_failed, settlement_requires_intervention or settlement_unconfirmed.
Decide. A return after delivery is an accounting correction, not a change to the financial result. A succeeded payment does not become failed because settlement came back, and deposit.reversed does not imply an on-chain clawback.
Act. Reverse the credit on your side. Nothing resends automatically and there is no automatic replacement, so do not tell the customer value is on its way back.
A payment needs intervention: hold and escalate
Detect.operationalState is requires_intervention and payment.requires_intervention fires. operationalReasonCode names the cause.
status is preserved exactly, so reading status alone says the payment is healthy when it is not. retrying is the opposite case: automation is still working and needs nothing from you.
Act. Put the payment in front of a named person with its id, your externalReference and the customer attached. Hold whatever credit you were about to give. Do not create a replacement payment, and do not retry the original request. Nothing builds that queue for you; the events that populate it are on Platform tools.
When you do not know what happened
1
Create nothing new
No replacement payment, no second send, no fresh idempotency key. The first attempt may have succeeded.
2
Replay the identical request with the identical key
An exact replay returns the original result rather than creating a second payment.
3
Read the payment
By id, or by your
externalReference through GET /v2/payments. See Reconciliation.4
If it needs intervention, stop
Escalate with the payment id. Retrying does not move it forward.
Next: Platform tools
The events to subscribe to, and how to rehearse in sandbox.