CoinPay

Invoice lifecycle

Statuses, timers, and what each signal means for fulfillment.

An invoice is created via POST /v1/invoices. The customer pays on hosted checkout (checkoutUrl). You learn about progress from webhooks and optional status polling.

Happy path

CREATED → CONFIRMING → PAID → SETTLED
  1. You create the invoice → customer opens checkout and picks chain/token
  2. Transfer detected → CONFIRMING / invoice.payment_detected
  3. Chain finality within the checkout window → PAID / invoice.paid (fulfill here)
  4. Platform sweep completes → SETTLED / invoice.settled (accounting)

Statuses

StatusMeaning
CREATEDWaiting for payment
CONFIRMINGTransfer seen; waiting for finality
PAIDPaid in time — safe to fulfill
UNDERPAIDPartial payment; top-up window open
EXPIRED_UNDERPAIDTop-up window ended still short
LATE_PAIDPaid after the 1h window — funds accepted, not standard fulfillment
OVERPAIDAmount above required (ops review; no auto-sweep in Phase 1)
SETTLEDSweep done; fee split applied
EXPIREDNo payment before the 1h window ended

Timers

TimerDurationStarts
Checkout window1 hourInvoice creation
Underpaid top-up30 minutesFirst partial payment

There is no separate grace setting. Payment that finalizes after the 1h window becomes LATE_PAID, not PAID.

What to fulfill on

SignalFulfill goods?
invoice.paid / status PAIDYes
invoice.late_paid / LATE_PAIDNo (unless you explicitly opt in)
invoice.settled / SETTLEDAccounting only — money moved
invoice.underpaidWait for top-up
invoice.expired / invoice.expired_underpaidClose the order

Polling (optional)

Prefer webhooks. If you must poll:

curl http://api.localhost:4000/v1/invoices/<id>/status \
  -H "Authorization: Bearer test_..."

Returns status, amount, amountReceived, and expiresAt.

Full detail: Get invoice.

Amounts and checkout

  • Amounts are decimal strings in major units (up to 6 decimal places). "10.50" = 10.50 USDC/USDT.
  • Token/chain are chosen at checkout, unless the merchant locked them with networkId / token on create — then checkout shows a fixed route.
  • Create/get responses include deposit addresses with chainName and the tokens accepted on each chain — only those tokens should be sent to that address.
  • Deposit addresses are pre-computed only for chains enabled for that invoice environment (test → local/testnets, live → mainnets), optionally filtered by create constraints.

Reorgs

Rarely, a previously seen payment can be invalidated. You receive invoice.payment_invalidated. Do not fulfill solely on payment_detected — wait for invoice.paid.

On this page