BTCPay invoices: check settlement before fulfillment
A practical first pass when a buyer says they paid but the order has not moved. Separate payment observations, invoice settlement and completed delivery.
A buyer says they paid. Your order still looks unpaid. Before changing a status or shipping anything, separate three questions: what payment did BTCPay Server observe, what state is the invoice in, and what did your application deliver? A checkout screenshot answers none of those reliably.
This is a free troubleshooting guide, not a diagnosis of your server. The first pass below may take about ten minutes when you already have authorized API access. Node synchronization, missing transactions and application recovery can take longer. Do not send anyone your API key, webhook secret, seed phrase or private key.
Read the invoice, not just a payment
Use authenticated Greenfield reads against your own BTCPay Server. Scope the key to the intended store with btcpay.store.canviewinvoices. Keep the credential in your existing secret manager; never put its value in a support ticket or screenshot.
Start with GET /api/v1/stores/{storeId}/invoices/{invoiceId}. Confirm the store and invoice match the order, then read status and additionalStatus together. The invoice status values in the pinned source are New, Processing, Expired, Invalid and Settled. The additional-status model lists None, PaidLate, PaidPartial, Marked and PaidOver.
Next, read payment methods at the same invoice route with /payment-methods appended. Compare the expected method and destination, amount, due, totalPaid and payments with the invoice and order. Payment entries have their own Invalid, Processing or Settled status.
A Settled payment is not a Settled invoice. One payment can be confirmed while the total received is insufficient for the order. A positive outstanding amount needs investigation, not a claim that someone held the invoice. An empty payments list means this response contains no recorded payments for that method at that time; it does not prove the buyer never sent funds.
Check amount, timing and the node
If the amount is short, inspect the store’s observed underpayment tolerance and the invoice state. Do not assume a documented default is your configured value. Do not change the tolerance simply to make an order pass. Route partial payment to an explicit review or payment-completion policy.
For on-chain payments, compare the confirmation count with the store’s requirement. Processing means the invoice is awaiting settlement conditions, not that it is settled. The stores FAQ explains that replace-by-fee transactions require at least one confirmation even when the merchant otherwise accepts zero-confirmation payments. Confirm the behavior of your installed version.
When a transaction appears on the network but not in BTCPay, compare the actual destination and amount with the invoice, then check node synchronization and the selected payment method. A public explorer can help inspect an on-chain transaction, but it neither matches the order for you nor establishes a Lightning payment. Consider transaction privacy before sharing identifiers with an explorer.
For Expired or Invalid invoices, check the timing and additional status. A late payment, insufficient amount or confirmation delay needs its own decision. Lightning does not wait for Bitcoin block confirmations, but an attempted payment or a wallet’s success screen is not a substitute for the server’s matching invoice and payment records.
Separate manual overrides and test invoices
Marked means the invoice status was set manually. Settled with additionalStatus Marked does not establish that a chain or Lightning payment occurred. Find independent payment evidence and the override’s audit record before treating the order as paid.
A zero-amount invoice can complete without an external payment. It is useful for testing some application behavior, but it proves neither paid checkout nor external revenue. Internal self-payments belong in test records too.
Make fulfillment a separate, recoverable decision
For ordinary automatic fulfillment, require an authenticated read showing the correct invoice Settled, then check additionalStatus, a real nonzero order and payment records consistent with the amount your policy accepts. Do not automatically treat Marked, PaidPartial or an unknown status as payment proof. Handle PaidLate and PaidOver under explicit policies rather than making an automatic refund or shipping decision from their names.
Keep a durable fulfillment record keyed to the store, invoice or mapped order, and the promised delivery action. Concurrent webhooks and reconciliation jobs must share that record. Use an atomic claim, a downstream idempotency key where supported, and a recorded completed result. A crash after dispatch is ambiguous until the actual delivery is checked; a database flag alone cannot guarantee exactly-once delivery.
If BTCPay shows settlement but the order is still waiting, investigate the fulfillment layer separately. A successful payment does not prove that a file was delivered, access was granted or goods were shipped. The companion webhook guide covers delivery history and replay without turning a retry into another order.
Leave a useful support record
Record the observation time, installed BTCPay version, store and invoice reference, status and additionalStatus, amount due, relevant payment observations, and the order’s fulfillment state. Remove credentials and unnecessary buyer information. Write NOT FOUND for anything you could not observe.
These checks narrow the failed layer. They do not authorize a manual status override, refund, configuration change or duplicate fulfillment. Review the actual evidence before any of those actions.
Sources were checked on October 8, 2026. API fields and controller behavior are linked to a fixed upstream commit below. Documentation and your installed release can differ; confirm the applicable version before changing production.
Sources
- BTCPay Server: store and invoice FAQ ↗
- BTCPay Server: ecommerce integration guide ↗
- BTCPay Server: Greenfield API integration guidance ↗
- Invoice controller: authenticated reads and manual status routes (pinned source) ↗
- Invoice payment-method fields and payment status (pinned source) ↗
- Additional invoice status values (pinned source) ↗
Educational commentary from Loop XXI. The block height records Bitcoin network time at publication.
Support this research with sats: pay@loopxxi.com · Send a tip.