Statuses
Invoice statuses
| Status | Meaning | Terminal? |
|---|---|---|
pending | No payment seen yet | no |
seen | Payment detected in mempool (0 conf). Once seen, we do not auto-expire even if expires_at elapses; the next block decides. | no |
paid | Payment confirmed (≥ conf_threshold) and amount within amount_sats ± amount_tolerance_sats | no (can become reverted via reorg) |
expired | pending past expires_at with no detected payment | no; address still watched through the grace window for a possible late_paid |
late_paid | Confirmed payment arrived after expires_at but inside the grace window | no (can become reverted) |
underpaid | Confirmed amount is less than amount_sats − amount_tolerance_sats. Buyer can top up; we auto-merge. | no |
overpaid | Confirmed cumulative amount exceeds amount_sats + amount_tolerance_sats | no; you may want to refund the difference |
requires_review | Routing landed on "no-match" (payment to a recycled address with no matching invoice), or cross-check disagreed. Manual admin action. | no |
reverted | Previously paid, then a chain reorg removed the tx | yes |
cancelled | Vendor cancelled before payment | yes |
Top-up payments (short-pay recovery)
If a buyer sends less than the invoice amount (outside tolerance), the invoice becomes underpaid and the watcher keeps listening. When a second on-chain transaction lands on the same address:
- Sum all non-reverted
paymentsfor that invoice plus the new tx. - If the total lands within
[amount_sats − tolerance, amount_sats + tolerance], the invoice flips topaid(orlate_paidif past expiry). - If still short, it stays
underpaidandamount_paid_satsreflects the new total. - If the total exceeds
amount_sats + tolerance, it becomesoverpaid.
Webhook implication: you may receive invoice.underpaid more than once for the same invoice, then invoice.paid / invoice.late_paid / invoice.overpaid when the cumulative total settles. Deduplicate by event_id only, never by invoice.id.
Hosted checkout surfaces this automatically: an underpaid invoice shows a "Send remaining X sats" CTA with a fresh bitcoin: URI for only the remaining amount.