Skip to main content
Back to blog
Engineering·Apr 30, 2026·7 min read

Reorg-safe payment confirmation: the failure mode nobody puts in their docs

Your customer pays, your dashboard says confirmed, then the chain re-organizes and the deposit transaction is no longer on the canonical chain. Here's how OpenSettle detects that, flips the state machine, and notifies the merchant — and how to design billing systems that don't blink when it happens.

OT
OpenSettle Team· Platform engineering

Every billing-on-blockchain tutorial walks through the happy path: customer signs, payment broadcasts, chain confirms, webhook fires, merchant provisions. The customer is happy. The merchant is happy. Then the chain reorganizes the next day and the deposit transaction is no longer on the canonical chain. Now nobody is happy, and the merchant doesn't know about it because their billing system told them the payment was confirmed two confirmations later.

This post is about that failure mode — how to detect it, how to model it as a state transition, and how to surface it to merchants without flooding them with noise.

What a re-org actually is

A chain re-organization is when the local head a node believes in is replaced by a heavier or longer chain. From the chain reader's perspective, blocks that were previously canonical are suddenly not canonical. Any transaction in those displaced blocks goes back to the mempool to be re-included (or not).

Reorgs come in two flavors that matter for billing. Shallow reorgs (one or two blocks deep) happen routinely on chains with probabilistic finality — they're a normal part of how the chain converges. Most production billing systems already wait past this depth before they consider a payment confirmed. Deep reorgs (more than two blocks, or any reorg through finality on PoS chains) are rare but not zero. Network partitions, sequencer outages on L2s, and contentious upgrades have all produced deep reorgs in the past 24 months.

If your billing system marks a payment confirmed after one block and a deeper reorg later excludes the transaction, you have a confirmed payment in your database that no longer corresponds to anything on-chain. That state is unsafe to act on, and "act on" is exactly what a billing webhook is — provision access, ship the order, mint the receipt.

The state machine

OpenSettle's payment state machine has explicit re-org states:

  • pending — payment intent created, no on-chain action yet.
  • confirmed — deposit transaction is on the canonical chain at sufficient depth for the chain's risk profile. Merchant receives payment.confirmed.
  • reorg_suspected — chain reader detected head divergence at a depth that could affect this payment. Merchant receives payment.reorg_suspected. The payment is no longer safe to act on, but no permanent decision has been made yet.
  • reorged — the deposit transaction is no longer on the canonical chain after the verification window closed. Merchant receives payment.reorged. The payment is invalidated and the merchant should reverse any side effects (de-provision access, cancel the order, refund the credit they extended).
  • finalized — cryptoeconomic finality reached for this chain. The payment is permanently safe. Merchant receives payment.finalized.

Three things to notice. First, reorg_suspected is reversible — if the suspected reorg doesn't materialize, the payment transitions back to confirmed. Second, reorged is a terminal state for the payment, but it's not an error — it's a fact about the chain, and the merchant's response is a business decision (provision differently next time, hold a hard wait on this customer, etc.). Third, the suspected → reorged distinction is what lets you build an "alert me, don't panic me" UX. Suspected fires as a yellow flag; reorged as a red one.

Detection

The chain reader watches the chain head on each chain. When it detects that the head has switched to a fork that excludes blocks we previously considered canonical, it computes the re-org depth and lists the displaced transactions. Any payment whose deposit transaction is in the displaced set transitions to reorg_suspected, and the payment goes back into the verification queue.

The verification window is chain-specific. For sequencer-driven L2s like Base, the window is short — the canonical chain settles quickly and a suspected reorg either resolves or persists in seconds (chain-dependent). For Ethereum mainnet through finality, the window is longer because the underlying finality clock is longer. The chain reader uses each chain's own finality model to decide when to commit to reorged.

What the merchant sees

Webhooks fire on every transition. The merchant's webhook handler can choose to ignore reorg_suspected (treat suspected reorgs as ephemeral, only act on confirmed reorged events) or to react immediately (immediately put the payment in "soft hold" pending verification). Both patterns are valid; the right one depends on how much the merchant trusts the customer and how reversible the merchant's product action is.

Inside the dashboard, payments in reorg_suspected get a yellow status badge with a tooltip explaining the in-progress verification. Reorged payments get a red destructive badge — the same visual treatment as a failed payment — and surface in the standard payments filter under a "Reorged" option. We shipped both UI surfaces alongside the state machine because there's no point detecting reorgs if the operator workflow doesn't let humans triage them.

Operator workflow

For most reorgs, no human action is needed. The state machine flips, the webhooks fire, the merchant's automated handler does the right thing, and the dashboard reflects the new state.

For the edge cases — a customer disputing that their payment was reorged, an unusual on-chain situation where the chain reader's view diverges from the merchant's wallet's view — there's a reorg-admin resolution flow in the dashboard. An admin can manually move a payment between the reorg states with an audit trail attached. This is rarely used and intentionally hard to access (step-up auth required), but it exists because real systems have edge cases that automation gets wrong.

What you should do if you're not on OpenSettle

The pattern generalizes. If you're building any on-chain billing system, the state machine for re-org safety wants four properties:

  • A confirmed state that's based on a chain-specific wait, not a single global "N confirmations" constant.
  • A reversible suspected state that lets you pause merchant-side actions without committing to a refund.
  • A terminal reorged state that the merchant's system can act on as a definitive signal.
  • Webhooks for every transition, including the reversible ones, so the merchant's handler can be a state machine instead of a polling loop.

Get those four right and you've moved the "the chain rolled back our deposit" problem from "rare and catastrophic" to "rare and routine."