Payment lifecycle
Understand payment intent state, clearing progress, and what wakes a payment that is waiting.
A payment intent is the immutable request. The clearing transaction records how that request moves through execution and settlement.
CREATED → QR_PARSED → PRICE_LOCKED → PAYMENT_PENDING
→ ASSET_RECEIVED → CLEARING → SETTLING → SETTLED → SUCCESSAny non-terminal clearing state can move to FAILED. Steps are idempotent and resumable: a retry uses persisted state and cannot duplicate ledger postings or provider calls.
Use GET /v1/payments/:id with either a payment intent ID (pi_…) or clearing transaction ID (clr_…) to render current status and the ordered timeline.
Where a payment waits
Two states can hold for a while, and PAYMENT_PENDING is woken by a different thing on each execution path:
| State | Waiting for | Path | Woken by |
|---|---|---|---|
PAYMENT_PENDING | the payer’s transfer to confirm | deposit-match | the wallet watcher, at the configured depth |
PAYMENT_PENDING | a PaymentCompleted log | on-chain-contract | the settlement indexer |
PAYMENT_PENDING | the authorization to be broadcast | x402 | the settle step, after reading the chain back |
SETTLING | the payment rail to confirm | any | the provider, or a resume |
This is why a status poll is not a substitute for a webhook: how long PAYMENT_PENDING holds is a property of the payer’s behaviour and the chain, not of your request.
What a status means
SUCCESSis terminal and means the merchant is credited and the postings balance.FAILEDrecords why, and is terminal unless the payer’s funds had already arrived (see below). A quote that expired past its deadline fails withQUOTE_EXPIRED; it never re-quotes, because a new price needs the payer’s consent.- Everything else can still move.
An expired lock on the contract path is enforced on-chain as well, so a payment failed here cannot settle later behind your back.
Retrying a failed payment
A payment can fail after the payer’s money arrived — a swap pool missing from the configuration, a settlement asset not yet enabled — while the funds sit at the deposit address. Once the cause is fixed, retry it:
curl -X POST https://api.mayarin.xyz/v1/payments/pi_01KZ…/retryThe retry reopens the payment at ASSET_RECEIVED and runs the remaining steps again. Each step is keyed per state, so nothing that already happened — a ledger posting, a sweep — happens twice. On the deposit path, an order whose deadline passed while the payment sat failed is re-signed with the same terms and a fresh deadline: the merchant’s locked amount does not change.
The reopen is recorded in the timeline with the failure it replaces, and the intent goes back to PROCESSING, so your webhook endpoint hears about the retry like any other state change.
A retry is refused with 409 when the payment has not failed, or when its funds never arrived — there is nothing to settle, and the payer should start a new payment. The dashboard shows a Retry settlement button on a failed payment.
Treat a webhook as a signal
A webhook wakes your integration; it is not the authoritative record. Mayarin applies the same rule internally — a provider webhook wakes the clearing engine, which then asks for the authoritative status, so a spoofed or replayed delivery cannot settle a payment on its own.
Do the same: verify the signature, then re-query GET /v1/payments/:id and act on what it says. See webhooks.