Skip to content
onchain-engineeringpayment-statusblockchain
Joshua C.

When Is a Crypto Payment Really Final? Confirmations and Receipts

Onchain payments are only final after confirmations, due to the risk of reorgs. This post explains how to track payment status, with code and real examples.

When Is a Crypto Payment Really Final? Confirmations and Receipts

Finality is a word that gets tossed around a lot in crypto payments. For most users, a transaction shows up in a block, and that is the end of the story. If you are building an onchain billing system, or issuing receipts for stablecoin payments, the picture is more nuanced. There are real risks in treating a transaction as confirmed too early. This post explores why blockchain confirmations matter, how reorgs can affect the payment status you show users, and why a receipt should wait until the risk is low enough.

What Does It Mean for a Transaction to Be Confirmed?

On Ethereum and similar chains, every new block builds on the previous one. When a transaction is included, it sits inside a block. But as every engineer who has tracked a transaction knows, the first block is not always the last word. Chains can reorg. The transaction might move, or even disappear, if a competing chain fork becomes the canonical one.

So, what is a confirmation? In practical terms, it is the number of blocks mined on top of the one containing your transaction. Each block added makes it less likely that a reorg will roll back your transaction. But until the block is buried under several others, the risk is not zero.

Why Reorgs Matter for Payment Status

A blockchain reorg (reorganization) is when the network discards one or more blocks and replaces them with others. This is rare on mature chains, but it does happen. If your app treats a transaction as final after the first block, you risk giving a receipt for a payment that is later reverted.

Let us see a simplified example. Suppose you accept a USDC payment on Polygon. Your backend watches for an incoming transfer:

from web3 import Web3

w3 = Web3(Web3.HTTPProvider('https://polygon-rpc.com'))

tx_hash = '0x1234...'

tx_receipt = w3.eth.get_transaction_receipt(tx_hash)
if tx_receipt and tx_receipt['blockNumber']:
    # Transaction is in a block, but is it confirmed?
    latest = w3.eth.block_number
    confirmations = latest - tx_receipt['blockNumber'] + 1
    if confirmations >= 12:
        # Consider payment final
        print('Payment settled, issue receipt')
    else:
        print(f'Only {confirmations} confirmations, wait longer')

Networks vary, but 12 confirmations is a common threshold for Polygon and Ethereum. If you issue a receipt after just 1 confirmation, and a 2-block reorg happens, you may have acknowledged a payment that never actually settled.

How Often Do Reorgs Happen in Practice?

On chains like Ethereum mainnet and Base, deep reorgs are rare. But shallow reorgs of one or two blocks are not unheard of. For example, data from ethstats.net shows that minor reorgs can occur a few times a day on mainnet. Most payments will not be affected, but the risk is not zero.

Polygon, with its shorter block times, can see more frequent short reorgs. For a system that issues receipts, the cost of a false positive (a payment that disappears) is a support nightmare. This is why most serious payment processors wait for a conservative number of confirmations.

Tracking Payment Status: Pending, Confirmed, Failed

A good onchain billing system tracks each payment through distinct states:

  • Pending: Transaction is seen in the mempool or included in a block, but not enough confirmations yet.
  • Confirmed: Transaction has reached the required number of confirmations. Payment is final.
  • Failed: Transaction was dropped, replaced, or reverted in a reorg.

Here is an example model you might use:

// TypeScript pseudo-model
export type PaymentStatus = 'pending' | 'confirmed' | 'failed';

interface Payment {
  txHash: string;
  status: PaymentStatus;
  confirmations: number;
  from: string;
  to: string;
  value: string;
  blockNumber?: number;
}

The status only moves to “confirmed” once the confirmation threshold is met. If a reorg removes the block, you update to “failed” and notify the user.

The Cost of Issuing Receipts Too Soon

Suppose you accept a $100 USDC payment. You see the transaction on Base after 1 block and issue a receipt immediately. A minute later, a 2-block reorg occurs and the payment is gone. Now you have given the user a service or asset without actually getting paid. Rolling this back is not possible. Blockchain settlement is final, but only for transactions that remain in the canonical chain.

This is not hypothetical. On heavily used NFT drops and during network congestion, reorgs become more common. If your system is not careful, this scenario can and will happen.

Real-World Numbers: How Much Confirmation Is Enough?

Different teams use different thresholds. Some exchanges use 12 blocks on Ethereum (about 2–3 minutes). For Polygon, 128 blocks (about 2.5 minutes) is not uncommon due to shorter block times and higher risk of shallow reorgs. Base is similar to Ethereum, and 12–15 blocks is typical.

For most stablecoin payments, waiting 2–3 minutes is a small price for certainty. The user experience is not harmed by a short wait, while the risk of a false receipt is greatly reduced.

Code Example: Polling for Confirmation

Here is a Python example using Web3.py to poll for payment confirmation:

def wait_for_confirmation(w3, tx_hash, min_confirmations=12):
    while True:
        receipt = w3.eth.get_transaction_receipt(tx_hash)
        if not receipt or not receipt['blockNumber']:
            print('Transaction not yet mined')
            time.sleep(5)
            continue
        current_block = w3.eth.block_number
        confirmations = current_block - receipt['blockNumber'] + 1
        if confirmations >= min_confirmations:
            print('Confirmed')
            return True
        print(f'Waiting for confirmations: {confirmations}/{min_confirmations}')
        time.sleep(10)

This pattern is robust and easy to audit. Do not move a payment to “confirmed” until this loop exits.

How Reclack Handles Confirmations and Receipts

We only mark a payment as complete, and issue a downloadable receipt, after it has reached the required number of confirmations on-chain. Our API tracks every payment through pending, confirmed, and failed states. We do not assume a payment is final until the canonical chain says so. This reduces risk for both merchants and customers. You can read more about our onchain payment flow and engineering practices at reclack.com.

onchain-engineeringpayment-statusblockchain