Core Concepts
Payment Lifecycle
Once a payment arrives, the server settles it automatically — it sweeps funds to your wallet, refunds any excess, and handles expiries. You don't trigger any of it. You just observe the status.
The one status that matters most
swept is terminal success: the money is in your destination wallet and the settlement object tells you exactly how much. Drive fulfillment off swept (or the intent.swept webhook), never off the browser.
Status values#
status is the canonical lifecycle state. /status also returns a friendlier action alias:
| status | action | Meaning |
|---|---|---|
| pending | waiting | Created, no payment detected yet. |
| underpaid | underpaid | Received less than expected. Waiting for the rest. |
| paid | paid | Received the expected amount. About to sweep. |
| overpaid | overpaid | Received more than expected. Will sweep expected + refund excess. |
| settling | paid | Settlement transaction in progress. |
| swept | swept | Terminal success. Funds are in your destination wallet. |
| refunding | refunding | Being refunded (cancelled with a partial balance). |
| refunded | refunded | Terminal. Funds returned to the sender. |
| cancelled | cancelled | Terminal. The checkout was cancelled: either the 10-minute window closed with nothing received, or it was cancelled explicitly. No funds were kept. |
| refund_failedsettle_failed | refund_failed | Terminal-ish. Automatic settlement/refund exhausted retries; needs manual attention. |
There is no “expired” status
An intent that runs out of time is written cancelled (or refunding when a partial balance can go back), so nothing ever sits pending forever.
Lifecycle flows#
The same states, drawn as the paths a payment actually takes. Each node carries a text label as well as colour, and the legend explains the palette.
Happy path
buyer selects an asset, pays in fullOverpayment
expected → wallet, excess → senderUnderpayment → completion
sender sends the restUnderpayment → expiry (SOL)
partial returned to senderCancellation — nothing received
timeout or explicit cancelCancellation — partial SOL, known sender
balance auto-returnedCancelling a checkout#
A checkout leaves pending/underpaid and becomes terminal in one of three ways:
- Automatic timeout. Any intent past its 10-minute
expires_atis cancelled by a background sweep. There's a small latency between the 10:00 mark and the sweep, so the effective window is 10:00 to about 10:30 at worst. - Explicit cancel by your backend:
POST /cancel { index }with yoursk_key (seePOST /cancel). - Explicit cancel by the client:
POST /intents/cancel { client_secret }, scoped to that one intent (used by the checkout SDK when the buyer closes the sheet).
In all three cases:
- Nothing received →
cancelled(no funds to move). - Partial SOL + a known sender →
refunding→refunded; the partial balance is returned to the sender automatically. - Partial funds but no unambiguous sender, or an SPL token →
cancelledwith the funds left on the deposit address for manual return (token partial-refunds aren't automated). - A wrong token (a mint the intent doesn't accept) is a separate path, not a cancellation: it's refunded to the sender automatically.
- A payment that lands after cancellation is not lost: a late deposit on a
cancelledintent is routed torefundingso it's returned.
Cancel is race-safe and idempotent: it only acts while the intent is pending/underpaid (a payment that already advanced the row wins, and the cancel is refused with 409), and cancelling an already-cancelled intent returns 200.
Both cancellation and timeout emit a webhook
intent.cancelled when nothing was received, or intent.refunding followed by intent.refunded when a partial balance goes back. See Webhooks.