Event types
The full list of webhook event types, what they mean, and which are terminal.
Every invoice state transition emits exactly one webhook event. The table below is the canonical list, rendered from the source-of-truth enum in the backend.
| Event type | Description | Terminal? |
|---|---|---|
| invoice.created | Fired when a new invoice is created. Safe to use to confirm the merchant record received the invoice request. | No |
| invoice.pending | A deposit to the invoice address has been observed on-chain but not yet reached the required confirmation count. | No |
| invoice.confirming | Deposit is accumulating confirmations but still below the target threshold. | No |
| invoice.confirmed | Payment reached the configured confirmation count and matches the expected amount. Fulfil the order. | Yes |
| invoice.expired | TTL elapsed without a confirming payment. Do not fulfil. | Yes |
| invoice.cancelled | The invoice was cancelled before payment (by the merchant or via API). Do not fulfil. | Yes |
| invoice.overpaid | Payment confirmed but the received amount exceeds the requested amount. Fulfil and optionally refund the surplus off-platform. | Yes |
| invoice.underpaid | Payment confirmed below the requested amount. Contact the buyer or refund off-platform; do not fulfil by default. | No |
| invoice.late_payment | A confirming payment arrived after the invoice expired. Treat as exceptional and reconcile manually. | Yes |
Terminal vs non-terminal#
- Terminal events signal a final state. You should stop polling the invoice and decide whether to fulfil, refund, or escalate.
- Non-terminal events are informational. They are useful for UX (e.g. showing "payment seen, waiting for confirmations" to the buyer) but must not trigger fulfilment.
Event ordering#
The platform emits events in transition order, but automatic retries (see Delivery semantics) mean a later transition can reach your server before an earlier one that is still being retried.
Always trust the status field in the payload — not the arrival order.
Idempotency#
Every delivery carries a unique X-Webhook-ID header. If you retry a delivery from the dashboard, the X-Webhook-ID stays the same — making idempotency on your side trivial: hash the pair (invoice_id, X-Webhook-ID) as a dedupe key.