Order Status
Order and payment status values, what moves them, and which webhook fires when they move.
Order Status
An order is the amount you asked to be paid. Its status is a rollup of the payments that have settled against it. The customer's transfer and its verification are tracked on the payment, not on the order.
const CheckoutOrderStatus = {
CREATED: 'CREATED',
PARTIALLY_FULFILLED: 'PARTIALLY_FULFILLED',
FULFILLED: 'FULFILLED',
CANCELLED: 'CANCELLED',
} as const;
Status Descriptions
| Status | Description |
|---|---|
CREATED | No settled payment yet. Payment attempts may be open, expired, failed, or cancelled underneath it. Every order starts here |
PARTIALLY_FULFILLED | At least one payment settled, but remainingUsdcAmount is still above the order's completion threshold. Further payment attempts are allowed |
FULFILLED | remainingUsdcAmount is 0, or a residual at or below the order's completion threshold. completedAt is set and the residual stays in remainingUsdcAmount |
CANCELLED | You cancelled a CREATED order that had no payment attempts. cancelledAt is set |
Completion threshold
An order's completion threshold is the greater of ORDER_COMPLETION_THRESHOLD_USDC (default
1 USDC) and requestedUsdcAmount × ORDER_COMPLETION_THRESHOLD_BPS / 10_000 (BPS default 100, or 1%).
Both the floor and the percentage are current settings applied on each rollup; changing either
applies to every order on its next rollup, including existing orders and late settlements.
For example, with the defaults, a 1,000 USDC order completes once 10 USDC or less remains,
while a 20 USDC order (1% is 0.2 USDC) completes once 1 USDC or less remains due to the floor.
A split-fee order uses the larger of that threshold and its split-fee pricing
tolerance. The residual stays in remainingUsdcAmount, and refunds use the settled payment amount.
Transitions
| From | To | Trigger | Webhook |
|---|---|---|---|
| none | CREATED | Order created through the SDK, the API, or the dashboard | ORDER_CREATED |
CREATED | CREATED (amounts change) | Customer resizes a dynamic order. Only while the order is untouched and has no open or expired payment attempt | ORDER_RESIZED |
CREATED | CREATED (amount saved) | Customer starts a payment with a new amount on an open-amount order. Only while no payment can still complete | ORDER_AMOUNT_SET, then PAYMENT_CREATED |
CREATED, PARTIALLY_FULFILLED | PARTIALLY_FULFILLED | A payment settles and the remaining amount is still above the order's completion threshold | PAYMENT_SETTLED |
CREATED, PARTIALLY_FULFILLED | FULFILLED | A payment settles and the remaining amount is 0 or at or below the order's completion threshold | PAYMENT_SETTLED, then ORDER_FULFILLED |
CREATED | CANCELLED | Merchant cancels from the dashboard. Rejected once any payment attempt exists | ORDER_CANCELLED |
Rules that follow from this:
- Orders never expire and never fail. There is no
EXPIREDorFAILEDorder status. The customer payment window applies to the payment, below. The dashboard's Active and Expired filters are derived from payment state, not stored on the order. PARTIALLY_FULFILLEDcannot be cancelled. Cancellation is only possible before the first payment attempt, and only from the dashboard, not with an API key.statusandremainingUsdcAmountalways agree in any snapshot.- An open-amount order has no amount until a payment starts. It stays
CREATEDwithrequestedUsdcAmountandremainingUsdcAmountset tonullandamountVersion0. Every payment start with a new amount saves both amounts and raisesamountVersionby one. chargebackStatusandrefundStatusare additive. Neither changesstatus.
Terminal States
| Status | Is Terminal |
|---|---|
CREATED | No |
PARTIALLY_FULFILLED | No |
FULFILLED | Yes |
CANCELLED | Yes |
New payment attempts are refused on a terminal order.
Payment Status
A payment is one customer attempt to pay an order on one rail. This is the
data.payment.status value you reconcile on in webhooks, and the status on rows from the
Payments API.
const CheckoutPaymentStatus = {
CREATED: 'CREATED',
SETTLED: 'SETTLED',
CANCELLED: 'CANCELLED',
EXPIRED: 'EXPIRED',
FAILED: 'FAILED',
} as const;
Status Descriptions
| Status | Description |
|---|---|
CREATED | Attempt open. The customer is inside their window, sending money or waiting for verification. quoteExpiresAt is the cutoff |
SETTLED | Settled onchain. netSettledUsdcAmount and fulfillTransaction are populated and completedAt is set. penalties lists any attestation-applied reductions, for example purchase protection |
EXPIRED | quoteExpiresAt elapsed while the attempt was still open. Not terminal: a late settlement can still land, and a recreate reopens it |
FAILED | The attempt could not be started onchain, or a crypto transfer stalled. A rejected verification does not move a payment here; it stays CREATED so the customer can retry. Usually terminal; a stalled crypto payment reopens if the deposit arrives late |
CANCELLED | The customer abandoned this attempt, for example by switching rail. Terminal on every rail except Zcash, where a deposit that lands on a cancelled attempt still settles it for 30 days after the attempt was created |
The customer payment window
quoteExpiresAt is set when the attempt is created: 1 hour for fiat rails,
6 hours 10 minutes for Relay, and the provider's deposit deadline for Zcash
(currently requested for 20 minutes). Follow the actual checkout countdown.
A fiat attempt still open at its cutoff becomes EXPIRED and fires PAYMENT_EXPIRED.
Live crypto attempts behave differently by provider. A Relay-backed attempt never becomes
EXPIRED: a transfer that fails or is refunded is marked FAILED at once, and one still
unresolved after about 6 hours 20 minutes is marked FAILED too, both firing PAYMENT_FAILED. A Zcash
attempt that is never funded does become EXPIRED, about 6 hours 20 minutes after it was
created. That backend stall window does not extend the displayed deposit deadline.
Eligible expired or cancelled Zcash attempts remain checked for late deposits for up to
30 days from creation, unless the provider quote is already FAILED or REFUNDED.
Sandbox crypto attempts expire on their quote window like fiat ones.
Transitions
| From | To | Trigger | Webhook |
|---|---|---|---|
| none | CREATED | Customer picks a rail on an open order | PAYMENT_CREATED |
CREATED | SETTLED | Settlement confirmed onchain, whether by buyer-verified proof, Seller Automated Release, or a crypto fill | PAYMENT_SETTLED, plus ORDER_FULFILLED when the order completes, plus PAYMENT_BRIDGE_PENDING when a payout bridge starts |
CREATED | EXPIRED | Fiat or sandbox quote window elapsed; live unfunded Zcash reached its backend stall window | PAYMENT_EXPIRED |
CREATED | FAILED | The attempt could not be started onchain, or a crypto transfer stalled | PAYMENT_FAILED |
CREATED | CANCELLED | Customer cancels or switches method on the checkout | PAYMENT_CANCELLED |
EXPIRED | SETTLED | Late onchain settlement after the window | PAYMENT_SETTLED, plus ORDER_FULFILLED when the order completes |
CANCELLED | SETTLED | A late Zcash deposit lands on a cancelled attempt | PAYMENT_SETTLED, plus ORDER_FULFILLED when the order completes |
EXPIRED, FAILED, CANCELLED | CREATED | A recreate reopens an eligible fiat attempt | None |
EXPIRED | CREATED | An intent extension lands with an expiry in the future, from an extend or from Peer support extending the onchain intent | None |
FAILED | CREATED | A stalled crypto deposit is observed settling late; settlement then follows | None, then PAYMENT_SETTLED |
SETTLED is terminal. A Relay crypto payment can settle from its onchain payout receipt before
Relay publishes its fee and deposit accounting. Within minutes Peer then refines
totalUsdcFeeAmount, referralFees, paymentAmount, currency and currencyPerUsdRate on the
settled payment without sending another event. netSettledUsdcAmount never decreases; it only
increases when another deposit to the same address also succeeded. Until Relay's accounting
arrives, a merchant-pays-fee order paid in a stablecoin is credited conservatively, about 0.2%
below the deposit. When that brief shortfall is above the order's completion threshold the order
shows PARTIALLY_FULFILLED with a slightly higher remainingUsdcAmount, then moves to
FULFILLED and sends ORDER_FULFILLED.
One cancellation is silent: when a customer starts a fresh Zcash attempt on an order that
already has an unfunded one, the old attempt is cancelled in the same transaction and no
PAYMENT_CANCELLED fires. CANCELLED can therefore show up in a read back with no event
behind it.
Reconciliation guidance: PAYMENT_EXPIRED and PAYMENT_FAILED do not touch the order. Treat
the order as done only on ORDER_FULFILLED, or when data.order.status is FULFILLED. Do
not close your side on a payment-level terminal event alone.
Chargeback Status
Orders and payments carry a chargeback status alongside their lifecycle status. A chargeback never changes FULFILLED or SETTLED; it is an additive fact recorded after settlement.
const OrderChargebackStatus = {
NONE: 'NONE',
PARTIALLY_CHARGEBACKED: 'PARTIALLY_CHARGEBACKED',
CHARGEBACKED: 'CHARGEBACKED',
} as const;
const PaymentChargebackStatus = {
NONE: 'NONE',
CHARGEBACKED: 'CHARGEBACKED',
} as const;
| Status | Description |
|---|---|
NONE | No settled payment on the order has been charged back |
PARTIALLY_CHARGEBACKED | Some, but not all, currently settled payments carry a chargeback |
CHARGEBACKED | Every currently settled payment carries a chargeback. A single-payment order goes here directly |
The order value is not monotonic: a later settlement can move an order from CHARGEBACKED back to PARTIALLY_CHARGEBACKED. Reconcile from the snapshot in each chargeback event rather than ranking transitions.
Refund Status
const CheckoutOrderRefundStatus = {
NONE: 'NONE',
PENDING: 'PENDING',
COMPLETED: 'COMPLETED',
} as const;
| Status | Description |
|---|---|
NONE | No refund on the order |
PENDING | Refund started; REFUND_PENDING emitted on the first move from NONE |
COMPLETED | Refund completed; REFUND_COMPLETED emitted |
Refunds apply to FULFILLED orders only.
Webhook Delivery Status
const WebhookDeliveryStatus = {
PENDING: 'PENDING',
DELIVERED: 'DELIVERED',
FAILED: 'FAILED',
} as const;
| Status | Description |
|---|---|
PENDING | Awaiting delivery or retry |
DELIVERED | Successfully delivered (2xx response) |
FAILED | All 7 attempts exhausted, or delivery aborted because the webhook was inactive, its URL no longer resolves to a public HTTPS host, or it was a test delivery whose single attempt failed |
See the retry policy for the schedule.