Invoice lifecycle

Status flow from invoice creation through settlement or expiry, with IPN trigger points.

Every invoice moves through a defined sequence of statuses. Understanding the lifecycle helps you write webhook handlers that respond correctly to each transition and know which statuses are safe to treat as final.

Status flow

waiting
  │
  ├─► unconfirmed ──► partially_paid ──► finished
  │                                    │
  │                                    ├─► expired
  │                                    ├─► failed
  │                                    └─► cancelled
  │
  └─► expired (no payment received before deadline)

In the common happy path a buyer sends the full amount before the invoice expires and the invoice moves waiting → unconfirmed → finished. The intermediate statuses cover under-payment, network confirmation delays, and exceptional outcomes.

Status descriptions

| Status | Terminal? | Description | |--------|-----------|-------------| | waiting | No | Invoice created; no on-chain transaction detected yet. The buyer has not sent funds. | | unconfirmed | No | A transaction has been broadcast to the network but has not yet reached the required confirmation depth. | | partially_paid | No | At least one payment was received but the total is below the invoice amount. The buyer may top up. | | finished | Yes | The full invoice amount was received and confirmed. Settlement is complete. | | expired | Yes | The invoice deadline passed before a sufficient payment arrived. | | failed | Yes | A processing failure occurred — for example, a chain-level error or an unrecoverable node error. | | cancelled | Yes | The invoice was cancelled explicitly — either by the merchant via the API, by the buyer using the hosted checkout cancel button, or by a platform operator. |

Terminal statuses (finished, expired, failed, cancelled) will not change. Once an invoice is in a terminal state you can safely archive it.

IPN triggers

IPNs are delivered when an invoice transitions into any of the following statuses:

| Status | IPN fired? | |--------|-----------| | waiting | No — creation event, no IPN | | unconfirmed | No | | partially_paid | Yes | | finished | Yes | | expired | Yes | | failed | Yes | | cancelled | No |

cancelled does not fire an IPN. If you need to detect cancellation, poll GET /v1/payment/:id or check the status in the invoice list.

For full IPN payload structure and HMAC verification, see Payment IPN.

Over-payment and partial payment

Partial payment (partially_paid)

The buyer sent less than the required amount. The invoice stays open until it either receives a top-up that brings it to the full amount (transitioning to finished) or the deadline passes (transitioning to expired). An IPN is fired for each partially_paid transition — there may be more than one if the buyer sends multiple under-amounts.

Over-payment

There is no dedicated overpaid status in the webhook flow. An invoice that receives more than the required amount still transitions to finished. The IPN payload includes the actual received amount alongside the expected amount so you can detect and handle over-payment in your handler.

Polling vs webhooks

Webhook delivery is the recommended approach for server-side fulfilment logic. For buyer-facing UIs where you need live status updates, poll GET /v1/payment/:id or use the embeddable widget which handles polling internally.

For more detail on reading invoice state from the API, see Reading invoices.