Seeing a payment in a block is not the same as having been paid. The gap between those two sentences is where merchants lose money.
A customer pays. Within seconds, the transaction shows up in the newest block on the chain. The naive payment system flips the invoice to paid, fires the webhook, and the merchant ships the order. Most of the time, nothing goes wrong.
The other times are the reason this post exists.
What a reorg is
A blockchain is a chain of blocks, each pointing back to the one before it. The catch is that at any given height, more than one valid block can be produced at nearly the same instant. Two miners or validators extend the tip independently, neither aware of the other, and for a short window the network holds two competing versions of recent history.
The protocol breaks the tie by picking one branch — usually the longest or heaviest chain — and discarding the rest. When the network switches from the branch it had been following to a different one, that switch is a chain reorganization, or reorg. The blocks on the abandoned branch are orphaned. They were part of the chain a moment ago; now they aren't.
Here is the part that matters for money: any transaction that lived only in an orphaned block is no longer confirmed. It drops back into the pending pool as if it had never settled. It might get mined again into a later block. It might not — if the sender's wallet doesn't rebroadcast, it can simply evaporate.
Reorgs are not an attack and not a malfunction. They are a normal, expected part of how public chains converge on a single history. Most are shallow — one or two blocks deep — and happen routinely right at the tip of the chain.
Which leads to the uncomfortable conclusion that drives everything else here.
The newest block is the most dangerous block
The deeper a transaction is buried under later blocks, the more work an attacker or an unlucky natural fork would have to redo to dislodge it. Each block stacked on top makes a reorg that reaches your transaction exponentially less likely.
The inverse is also true, and it's the one people forget. A transaction in the most recent block has nothing buried on top of it. It is maximally exposed to the next reorg. So the single worst moment to treat a payment as final is the exact moment it first appears — which is, of course, the moment the naive system acts.
height N ← deposit lands here (0 confirmations, reorg risk: high)
height N+1 (1 confirmation)
height N+2 (2 confirmations)
height N+3 ← safe enough for (3 confirmations, reorg risk: low)
this chain's threshold
Confirmations are the defense. They exist for exactly one reason: to outlast reorgs. A transaction isn't "more paid" at three confirmations because the money grew — it's safer because undoing it now would require rewriting three blocks of work instead of zero.
How deep is deep enough? It depends on the chain
There is no universal confirmation count, and any system that hard-codes one is wrong somewhere.
Chains with fast or deterministic finality bound reorgs to a small, fixed depth — once a block is final, it stays final, so a deposit can be safe quickly. Probabilistic chains like Bitcoin never make a transaction perfectly irreversible; they only make reversing it progressively, steeply harder with each block. A payment that's safe at a couple of confirmations on a fast-finality chain might need more blocks to reach the same comfort on Bitcoin.
That's why the right confirmation threshold is set per network, not as one number sprayed across every rail. We tune it for each chain's actual reorg behavior, which is why you'll see different settle times across the rails on the networks page. Bitcoin and a fast-finality chain reaching paid on different clocks isn't an inconsistency — it's the system respecting how each chain actually finalizes.
What halfin does instead
halfin credits deposits reorg-aware. The deliberate design choice is that detection and settlement are two separate events, and only the second one means "you've been paid."
When a deposit is first detected, the invoice does not jump to paid. It moves into a confirming state — a payment is on its way and blocks are accumulating underneath it. The invoice flips to paid only once the chain has reached the confirmation threshold for that specific network. In webhook terms, you get invoice.confirming first and invoice.paid second, and they are not the same signal.
If a reorg orphans the block that carried the deposit before it has settled, the credit does not stand. The invoice does not get stranded in a paid state on the strength of a transaction the chain has since dropped. Nothing about your accounting silently disagrees with the chain.
And if a deposit that had already begun confirming gets reversed by a reorg, that isn't swept under the rug. It surfaces as a distinct webhook event — invoice.deposit_reversed — so your integration can react to a reversal as deliberately as it reacts to a payment, instead of inferring it from a raw block count you'd have to track yourself.
Why this matters before you ship goods
The whole point of waiting is to put the confirmation window before the irreversible action, not after it.
If you ship a digital good, release a withdrawal, or hand over inventory the instant a deposit appears, you've coupled your irreversible step to the chain's most reversible state. A shallow reorg that orphans that block leaves you having delivered against money that quietly reverted to unconfirmed — and may never come back.
So the operating rule is blunt: act on invoice.paid, never on first sight. If your business genuinely needs a faster signal — to show the customer a "payment received, confirming" screen, say — invoice.confirming is the right event for that, precisely because it doesn't claim finality. Use it to reassure, not to release.
The reorg-safe pattern, end to end:
- Receive the webhook and verify its signature over the raw bytes before you trust a single field.
- Branch on the event type. Treat
invoice.confirmingas "in progress," not "done." - Perform the irreversible step — ship, release, credit — only on
invoice.paid. - Keep a handler for
invoice.deposit_reversedso a late reorg has somewhere to land instead of becoming a silent discrepancy.
There's a real cost to this: a few minutes of waiting on slower chains, where an optimistic competitor would have already flashed a green checkmark. We think that's the correct trade. A confirmation threshold is cheap. Shipping an order against money the chain decided never existed is not.
A confirmation isn't a formality you're waiting out. It's the chain telling you how hard it would be to take this money back — and we don't tell you you've been paid until the answer is "very."
R. Adeyemi, halfin payments engineering