Skip to content
Zuuppa
Dashboard

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.

Tip

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:

statusactionMeaning
pendingwaitingCreated, no payment detected yet.
underpaidunderpaidReceived less than expected. Waiting for the rest.
paidpaidReceived the expected amount. About to sweep.
overpaidoverpaidReceived more than expected. Will sweep expected + refund excess.
settlingpaidSettlement transaction in progress.
sweptsweptTerminal success. Funds are in your destination wallet.
refundingrefundingBeing refunded (cancelled with a partial balance).
refundedrefundedTerminal. Funds returned to the sender.
cancelledcancelledTerminal. The checkout was cancelled: either the 10-minute window closed with nothing received, or it was cancelled explicitly. No funds were kept.
refund_failedsettle_failedrefund_failedTerminal-ish. Automatic settlement/refund exhausted retries; needs manual attention.
Note

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 full
pendingpaidsettlingsweptterminal

Overpayment

expected → wallet, excess → sender
pendingoverpaidsettlingsweptterminal

Underpayment → completion

sender sends the rest
pendingunderpaidrest arrivespaidsweptterminal

Underpayment → expiry (SOL)

partial returned to sender
pendingunderpaidwindow closesrefundingrefundedterminal

Cancellation — nothing received

timeout or explicit cancel
pendingcancelledterminal

Cancellation — partial SOL, known sender

balance auto-returned
pendingrefundingrefundedterminal
Legend:terminal success (swept)in-flight / needs moreprocessingterminal / cancelled

Cancelling a checkout#

A checkout leaves pending/underpaid and becomes terminal in one of three ways:

  1. Automatic timeout. Any intent past its 10-minute expires_at is 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.
  2. Explicit cancel by your backend: POST /cancel { index } with your sk_ key (see POST /cancel).
  3. 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 receivedcancelled (no funds to move).
  • Partial SOL + a known senderrefunding refunded; the partial balance is returned to the sender automatically.
  • Partial funds but no unambiguous sender, or an SPL token cancelled with 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 cancelled intent is routed to refunding so 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.

Note

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.

© 2026 Zuuppa. All rights reserved.