Payment Lifecycle
Every status a payment can reach, the transitions between them, and what action (if any) to take.
A payment moves through a small number of states. Most payments follow the same line from creation to confirmed. Branches exist for partial pay, expiry, reorg, and refund.
Lifecycle
Normal path
pending
→awaiting_payment
→payment_detected
→confirming
→confirmed ✓
POST /api/v1/payments returns the payment in pending state. As soon as a chain is selected on the checkout (either by the customer or by your code via /checkout/{id}/select-chain), it flips to awaiting_payment. When the customer's transaction appears on-chain it becomes payment_detected, then confirming, and finally confirmed once the chain's confirmation rule is satisfied.
Cancellation and expiry
pending
/awaiting_payment
→cancelled
customer or merchant cancels before any funds arrive
pending
/awaiting_payment
→expired
window closed with no funds receivedUnderpayment
payment_detected
/confirming
→underpaid
cumulative short by more than the tolerance
underpaid
→confirmed ✓
customer tops up, or merchant accepts the shortfall
underpaid
→refund_requested
→refunded
customer requests refund, merchant marks refunded
underpaid
→expired_underpaid
window closes with the partial amount still short
Late-grace revival
expired
/expired_underpaid
→payment_detected
a late transaction that fully covers the invoice within the late-grace window
expired
/expired_underpaid
→underpaid
a late transaction that only partially covers. The merchant can still accept or refund
Both expired and expired_underpaid payments revive this way. Once revived, the payment is flagged so it is never auto-expired again. A revived underpaid payment waits for a top-up, a merchant accept, or a refund.
Reorg
confirming
/confirmed
→expired_reorged
confirming tx removed by a chain reorg after the payment window closed
confirmed
→underpaid
rare: reorg of a contributing tx drops the cumulative back under tolerance
State reference
Normal path
| Status | Webhook event | Meaning |
|---|---|---|
pending | none | Payment created. The customer has not picked a chain yet. |
awaiting_payment | payment.awaiting | Chain picked and the locked crypto amount displayed. Waiting for the customer to send. |
payment_detected | payment.detected | Transaction seen in the mempool or block. Not yet final. |
confirming | payment.confirming | On-chain. Waiting for the chain's required confirmations. |
confirmed | payment.confirmed | Final. Funds are in your wallet. Platform fee is deducted from Prepaid Credit. |
confirmed is the terminal success state. Safe to release goods or services on this event.
Overpayment
| Status | Webhook event | Meaning |
|---|---|---|
confirmed + overpayment flag | payment.overpaid | Customer sent more than the invoice amount. The payment is still confirmed. The event carries the excess amount. |
payment.overpaid fires only when the overpayment exceeds the per-merchant threshold (default 1%). Below that, the payment confirms silently.
Underpayment
| Status | Webhook event | Meaning |
|---|---|---|
underpaid | payment.underpaid | Cumulative received is below the invoice amount by more than the tolerance (default 0.5%). Invoice is still open. |
refund_requested | payment.refund_requested | Customer requested a refund from the checkout. Awaits merchant action. |
refunded | payment.refunded | Merchant marked the payment refunded after sending coins back. |
underpaid is non-terminal. The window is still open and the customer can top up. The merchant can also accept the shortfall via the dashboard.
Expiry and reorg
| Status | Webhook event | Meaning |
|---|---|---|
expired | payment.expired | Payment window closed with no funds received. A late transaction within the grace window can still revive it. |
expired_underpaid | payment.expired_underpaid | Window closed with a partial amount. A late transaction within the grace window can still revive the payment (to underpaid, where the merchant can then accept the shortfall). Otherwise the customer can request a refund from the checkout, and the merchant records the refund once sent. |
expired_reorged | payment.expired_reorged | A confirming or confirmed transaction was removed from the chain by a reorg after the payment window had closed. Does not revive. |
A payment in payment_detected or confirming that is fully paid (within tolerance) never expires on the timer. One that is still short beyond the tolerance when the window closes moves to expired_underpaid.
A late on-chain transaction within the per-chain late-grace window after expires_at revives an expired or expired_underpaid payment: full coverage moves it to payment_detected, partial coverage moves it to underpaid.
Cancelled
| Status | Webhook event | Meaning |
|---|---|---|
cancelled | payment.cancelled | Cancelled before any on-chain transaction was detected. Can be triggered by the customer (Cancel button on the checkout) or the merchant (DELETE /api/v1/payments/{id}). Returns PAYMENT_NOT_CANCELLABLE if the status is payment_detected or later. |
Failed
| Status | Webhook event | Meaning |
|---|---|---|
failed | payment.failed | The gasless settlement for this payment failed and no funds landed. Terminal and system-initiated. Never reached by a user or merchant cancel (those use cancelled). The payload's failure_reason carries one of insufficient_balance (customer's wallet held less than the payment amount at settlement time), estimate_revert (the token contract rejected the settlement simulation), or settlement_reverted (the settlement transaction was broadcast but reverted on-chain). The first two are pre-broadcast aborts. Today the x402 (gasless) relay is the only path that produces failed, either before broadcast or on a reverted settlement. |
Payment window
The invoice amount in crypto is locked for the payment window. When the window closes without confirmation, the payment expires. Late-grace revival is the period after expiry during which a late on-chain transaction can still revive the payment.
| Chain family | Payment window | Late-grace revival |
|---|---|---|
| Bitcoin, Litecoin, Bitcoin Cash, Dogecoin | 60 minutes | 12 hours |
| Ethereum, Base, Polygon, BNB Chain, Ethereum Sepolia | 20 minutes | 12 hours |
| Solana | 15 minutes | 12 hours |
| TRON | 15 minutes | 12 hours |
| XRP | 20 minutes | 12 hours |
| Stellar | 20 minutes | 12 hours |
| Monero | 2 hours | 12 hours |
The window is set per chain. When a payment accepts multiple chains, the longest window applies until the customer picks a specific chain. The window is not configurable per payment from the API.
Tolerances
Both tolerances are merchant-configurable in the dashboard under Settings → Payment preferences:
| Setting | Default | What it controls |
|---|---|---|
| Underpayment tolerance | 0.5% | Shortfalls below this confirm automatically. Above, the status becomes underpaid and the customer can top up or request a refund. |
| Overpayment threshold | 1% | Overpayments above this fire payment.overpaid with the excess amount. Below, the payment confirms silently. |
Both values are snapshotted onto the payment row at creation. Changing the setting later does not retro-shift open invoices.
Dashboard and API names
The API and webhooks use the snake_case status strings. The dashboard renders friendlier labels.
| Dashboard label | API / webhook status |
|---|---|
| Pending | pending |
| Awaiting payment | awaiting_payment |
| Payment detected | payment_detected |
| Confirming | confirming |
| Confirmed | confirmed |
| Underpaid | underpaid |
| Refund requested | refund_requested |
| Refunded | refunded |
| Expired | expired |
| Expired (underpaid) | expired_underpaid |
| Expired (reorg) | expired_reorged |
| Cancelled | cancelled |
| Failed | failed |
Safe confirmation
Do not release goods or services on payment_detected or confirming. Both can roll back.
Release only on payment.confirmed. For high-value orders, verify the webhook signature before acting. See Webhooks.