StableOps
FAQ

When orders expire and what happens if a payment never arrives

How order expiration works, when payment.expired fires, and what happens to a transfer that arrives too late.

Every payment order carries an expiresAt timestamp. If no matching transfer arrives before then, the order is moved to expired and a payment.expired webhook is sent.

How expiration is processed

A background worker scans for expired orders on a short repeating interval (roughly once a minute), so an order flips to expired shortly after expiresAt passes, not at the exact second. Each expiration runs in a transaction that:

  1. moves the order from created to expired,
  2. releases the addresses that were allocated to it back to the pool, and
  3. enqueues a payment.expired webhook.

Only orders still in created can expire. Once an order reaches detected, confirmed, or finalized, the deadline no longer applies. A payment already in flight is never dropped just because the clock ran out.

A payment that arrives after expiry

If a customer pays after the order has expired, the transfer will not revive the order:

  • The order is no longer in created, so the exact-match rule can't promote it.
  • The address may already have been released and reused by another order.

Handle this the same way as any other misdirected transfer: reconcile or refund manually against the address the funds landed on. With BYO addresses those funds are in an address you control.

Choosing an expiration window

  • Pick a window long enough for a real human to open a wallet, copy the amount, and have the transfer confirm. A few minutes is usually too tight; 15–60 minutes is a common range.
  • Don't set it so long that abandoned orders hold addresses (in single-address mode) far longer than needed.
  • If you don't need an order anymore before anyone has paid, cancel it instead of waiting for expiry. Cancellation also releases the addresses and emits payment_order.canceled. Only orders still in created can be canceled.

expired vs canceled

StateTriggerEmits
expiredexpiresAt passed with no matching transferpayment.expired
canceledYou called the cancel endpoint while still createdpayment_order.canceled

Both release any addresses still held by the order.

How is this guide?

Last updated

On this page