Statuses & expiry
Payment status values for pay-ins and pay-outs, plus the transaction expiry rules.
Generated from public-api.mdVersion 1.2.0Last updated 2026-09-20
#Payment Statuses
These are the statuses a transaction can hold. status is returned by
GET /v1/payments/{id} and at creation.
| Status | Description |
|---|---|
pending | Created, awaiting customer payment (deposit) or review/dispatch (withdraw) |
processing | Handed to the payment/payout provider |
pending_approval | Held for manual approval (e.g. a payout that needs review) |
completed | Successfully completed |
failed | Failed, or cancelled by you via POST /v1/payments/{id}/cancel |
expired | The deposit window elapsed without a matching payment |
refunded | A completed deposit was reversed by an operator |
cancelled | Defined in the data model but not produced for payments created through this API |
Transitions that are actually allowed:
pending→processing,pending_approval,completed,failed,expiredprocessing→pending_approval,completed,failed,expiredpending_approval→completed,failedcompleted→refundedfailed/expired→pending_approval,completed(this is how a slip upload or an operator rescue revives a transaction)refundedis terminal
Expiry produces
expired, notfailed. An unpaid deposit ends atexpiredwith an internal failure reason ofTransaction expired, and the webhook carriesstatus: "expired"/ eventtransaction.expired. (An earlier revision of this document claimed expiry mapped tofailed; that was never the behaviour of the shipped code.)
#Transaction Expiry
- Deposits expire 10 minutes after creation by default. The exact instant is
returned as
expires_at; always use that value rather than assuming a duration. - If the customer does not pay in time:
- Status becomes
expired(notfailed), with an internal failure reason ofTransaction expired. - A webhook is sent with
status: "expired"and eventtransaction.expired.
- Status becomes
- The expiry sweeper runs every 1 minute and considers
pendingandprocessingdeposits, so a webhook can arrive up to a minute afterexpires_at. - An expired deposit is not necessarily lost: if the customer did pay, upload the
slip within 24 hours via
POST /v1/payments/{id}/upload-slipto rescue and credit it.
A PromptPay QR does not expire at the bank. A customer can still scan and pay after
expires_at; that payment surfaces as an unmatched transfer for operations to resolve, not as an automatic credit.

