Engineering · 17 Aug 2026

Confirmation policy is a business decision, not a constant.

How many confirmations is enough? The honest answer depends on the network, the amount, and what you release when you say yes.

On this page

Key takeaways

  • "Confirmed" is a policy your business defines, not a fact the network hands you.
  • The right threshold depends on the network's finality model, the value at risk, and what confirmation releases.
  • Make the policy explicit per network — and per amount tier when the stakes vary.
  • Only enable networks the software actually implements and tests.

Ask three engineers how many confirmations make a payment safe and you will get four numbers. The disagreement is not ignorance. It is that the question is underspecified: safe for what, on which network, for how much?

In Vellum's deployment, confirmation policy was made explicit per network. This note explains why that was the right move and how to reason about it.

Confirmed is a decision

A transaction appears on a network, then becomes progressively harder to reverse. Somewhere along that curve your business decides to treat it as paid. That point is a policy, and it trades speed against risk.

  • Too early and you release goods for a payment that could still be reorganised away.
  • Too late and customers wait, support tickets rise, and the checkout feels broken.

Threshold too early

  • Goods released for a payment that could still reverse
  • Loss lands on the business
  • Fast, until it is expensive

Threshold too late

  • Customers wait and support tickets rise
  • Checkout feels broken
  • Safe, until the customer leaves

Three variables

VariableWhy it mattersTypical question
Network finality modelNetworks differ in how and when a transaction becomes hard to reverseWhat does finality mean on this network?
Value at riskA small purchase tolerates more risk than a large settlementWhat is the worst loss if this reverses?
What confirmation releasesDigital goods are hard to recall; a pending payout is easy to holdWhat happens irreversibly when we mark it paid?

Tiers, not a single number

A single global threshold is convenient and usually wrong. A more honest design has tiers by amount, per network. The numbers below are placeholders to show the shape — your values come from your own risk assessment and the network's characteristics.

Illustrative shape of a policy — values are placeholdersTypeScript
type ConfirmationPolicy = {
  network: string;
  tiers: { upToAmount: number; confirmations: number }[];
};

const policy: ConfirmationPolicy = {
  network: "example-network",
  tiers: [
    { upToAmount: 500, confirmations: 1 },
    { upToAmount: 10_000, confirmations: 3 },
    { upToAmount: Infinity, confirmations: 6 },
  ],
};

export function requiredConfirmations(amount: number) {
  return policy.tiers.find((t) => amount <= t.upToAmount)!.confirmations;
}

Separate the states

A payment should not jump from "not seen" to "paid". Distinct states let each part of your business act at the right moment:

  1. Seen. The transaction is visible. Show the customer progress.
  2. Confirming. Confirmations are accumulating. Hold fulfilment.
  3. Confirmed. Policy is met. Emit the signed event; fulfil.
  4. Settled. Funds are where treasury expects them.

Why it matters operationally

When the states are separate, a late confirmation and a missed webhook look different on the dashboard, and the runbook can treat them differently.

SeenVisible; show progress
ConfirmingAccumulating; hold fulfilment
ConfirmedPolicy met; emit event, fulfil
SettledFunds where treasury expects
Four states so each team can act at the right moment

Only enable what is implemented

StablePay lets you connect the chains, tokens, and RPC providers you operate, but support is limited to what is implemented and tested. We would rather ship a short list that is true than a long list that is aspirational. Anything not shipped is marked planned.

Operating the policy

  • Write the policy down and get sign-off from treasury, not just engineering.
  • Review it when you change networks, tokens, or typical transaction size.
  • Monitor RPC health: a lagging provider looks like a slow network.
  • Record which policy version applied to each payment, for later audit.

This is risk guidance from an engineering studio, not financial or legal advice. Set thresholds with your own risk owners.

Policy sign-off

  • Treasury, not only engineering, has approved the thresholds.
  • Each enabled network has its own written policy.
  • Amount tiers are defined where the stakes vary.
  • RPC health is monitored, so lag is not mistaken for a slow network.
  • Each payment records which policy version applied.
"Confirmed" is a promise your business makes. Make it deliberately.

How finality actually works

"Confirmed" hides two very different ideas of finality. Understanding which one your network uses is the foundation of a sensible policy.

ModelHow it worksWhat a confirmation meansExample
Probabilistic finalityThe chain with the most accumulated work or weight is canonical. Recent blocks can be replaced by a longer competing chainEach additional block on top makes reversal exponentially less likely, but never strictly impossible [1]Proof-of-work chains such as Bitcoin
Economic or deterministic finalityValidators vote to finalise checkpoints. Once finalised, reverting requires a large, provable economic penalty [2]A finalised block is treated as irreversible under the protocol's assumptionsProof-of-stake chains such as Ethereum

On probabilistic chains, the original Bitcoin paper [1] shows that the probability of an attacker catching up falls exponentially with the number of confirmations. That is why confirmation counts are a policy: you choose the residual risk you are willing to carry for a given amount. On proof-of-stake networks, finality is a protocol event, and Ethereum's documentation [2] describes checkpoints becoming finalised after validators attest across epochs.

Read the chain's own finality signal

Where a network exposes explicit tags — for example Ethereum's safe and finalized block tags in its JSON-RPC interface [3] — a policy can key on the tag instead of counting blocks. Use whatever the network's own documentation says is the strongest available signal.

Reorganisations: designing for the block that disappears

A reorganisation happens when the network switches to a different chain tip, replacing recent blocks. A transaction that was included may be dropped and re-included later, or not at all. A payment system that never expects this will, eventually, mark something paid that is no longer there.

SeenTransaction in block N
ConfirmingDepth increases
ReorgBlock N replaced
Rolled backState returns to seen or unseen
Re-includedOr never appears
A reorg from the gateway's point of view
  • Store the block hash, not just the number. A number can point to different blocks over time; the hash identifies the exact one you observed.
  • Re-verify before acting. Before fulfilling, confirm the transaction is still in the canonical chain at the required depth.
  • Make transitions reversible until final. A confirming payment can move back to seen. Only your policy's final state is one-way.
  • Alert on rollbacks. A reorg that touches a payment you have already acted on is a rare, high-value signal.
Illustrative — verify the observed block is still canonical before advancing stateTypeScript
async function stillCanonical(p: { blockNumber: bigint; blockHash: string }, rpc: Rpc) {
  const block = await rpc.getBlockByNumber(p.blockNumber);
  return block?.hash === p.blockHash; // same hash at that height => not reorganised away
}

async function advance(p: Payment, rpc: Rpc, policy: Policy) {
  if (!(await stillCanonical(p, rpc))) return rollback(p);            // demote to seen/unseen
  const depth = (await rpc.latestBlockNumber()) - p.blockNumber;
  if (depth >= BigInt(policy.requiredConfirmations(p.amount))) return confirm(p);
}

Do not trust a single RPC provider

Your view of the chain is only as good as the node answering your requests. A provider that is lagging, on a minority fork, or simply wrong will make your policy wrong with it. Treat providers as unreliable inputs to be cross-checked.

FailureEffect on paymentsMitigation
Provider lags behind the headConfirmations appear lateCompare head height across providers; alert on divergence
Provider returns stale dataA payment looks unconfirmedQuery a second provider before escalating
Provider is briefly on a forkA transaction flickers in and outRequire agreement or use a finality tag before acting
Rate limits or outageIndexer stallsFailover to a secondary provider; alert on indexer lag
Compromised endpointFalse dataRun your own node for high-value networks; prefer diversity

For higher-value flows, consider a quorum: act only when two independent providers agree on the block hash and depth. It costs an extra request and removes a whole class of failure.

Policy as code, with an audit trail

A confirmation policy written in a wiki drifts from the one running in production. Encode it as configuration in the deployment, review changes like code, and stamp every payment with the policy version that applied to it. Months later, when someone asks why a payment was released at three confirmations, the answer is a lookup, not a memory.

Illustrative policy file — values are placeholders, set them from your own risk assessmentJSON
{
  "version": "2026-09-01",
  "networks": {
    "example-network": {
      "signal": "confirmations",
      "tiers": [
        { "upToAmount": "500",    "confirmations": 1 },
        { "upToAmount": "10000",  "confirmations": 3 },
        { "upToAmount": null,     "confirmations": 6 }
      ],
      "providers": { "minAgreeing": 2 }
    }
  }
}

Change control for a policy

  • Changes go through review by treasury as well as engineering.
  • The version is recorded on every payment that it governed.
  • A new policy applies only to payments created after it takes effect.
  • Loosening a threshold requires a recorded reason.
  • The previous version is kept for audit.

Testing a policy before it meets real money

  1. 01

    Unit-test the tiers

    Assert the required confirmations at each boundary amount, including exactly-on-the-line values.

  2. 02

    Replay recorded chain data

    Feed historical blocks, including known reorganisations, through the indexer and assert the resulting states.

  3. 03

    Run on a test network

    Send real transactions on a public test network and watch each state transition on the dashboard.

  4. 04

    Inject faults

    Slow or block a provider and confirm that failover and alerts behave as documented.

  5. 05

    Go live with low limits

    Start with capped amounts, then raise them once a full reconciliation cycle has closed cleanly.

This is engineering guidance on system behaviour, not financial or legal advice. The thresholds that suit your business are a risk decision for your own risk owners.

Closing

"Confirmed" is a promise your business makes. Make it deliberately, per network and per amount, and write it where the next engineer can find it.

References & further reading

  1. 1
    Bitcoin: A Peer-to-Peer Electronic Cash System — Satoshi Nakamoto, 2008Section 11 calculates the probability of an attacker overtaking the honest chain.
  2. 2
    Proof-of-stake and finality (Gasper) — ethereum.org developer documentation
  3. 3
    JSON-RPC API — ethereum.org developer documentationSee the block parameter options, including the safe and finalized tags.
Found this useful? Share it

Get the next essay in your inbox

Practical writing on payments infrastructure, operations software, and shipping real systems. No spam, no sales sequence.

We only use your email to send the studio's writing. See the privacy policy.

About the authors

Product behaviour described here reflects what is implemented and tested; anything else is marked as planned. Code samples are illustrative.

All writing

Have a system like this to run?

Explore the catalog, or write down the problem and the constraints. We respond when the fit is real.

Free apps from the studio. Enter your email, get a private download link. Free for personal use.

Get them free

Have a product to sell? We review, list, and sell it for you — you keep 90% of every sale.

Apply to sell with us