Back to Blog
Engineering
2026-07-097 min readBy StableOps

How to design confirmation thresholds for stablecoin payments

A practical model for confirmations, reorgs, finality, and merchant fulfillment.

Stablecoin payments
Confirmations
Webhooks

A stablecoin payment can look like a simple transfer, but merchant systems need something stronger: a business event that can drive fulfillment, reconciliation, audit trails, and replay. The goal of a confirmation policy is not to optimize for raw speed. It is to make the trade-off between customer experience and chain-reversal risk explicit.

This article gives payment, order, and risk teams a shared decision framework: when to update the UI, when low-risk value can be provisioned, and when a transfer is safe to record in an irreversible business ledger. Chain-specific thresholds can change with network conditions and platform configuration, so business services should not hard-code a block count. They should react to a normalized payment lifecycle.

A wallet transaction is not enough

A successful wallet broadcast only means the transaction is moving through the network. Even if a transaction already appears in a block explorer, replacement transactions, reorgs, or temporary node disagreement are still possible. If a merchant provisions access immediately, it can end up in the hardest state to explain: the customer received the product, but the payment disappeared from the ledger.

StableOps models the lifecycle with four explicit states:

  • detected: the scanner found a matching candidate transfer in a block. It can drive a reassuring “payment received, confirming” UI, but it does not mean the funds are reliable yet.
  • confirmed: the transfer reached the current business confirmation threshold for its chain. It can support low-risk, reversible fulfillment.
  • finalized: the transfer reached a stronger finality threshold. This is the default trigger for reconciliation, ledger entries, and irreversible fulfillment.
  • reverted: confirmation tracking found that the chain fact was rolled back or the receipt failed. Any optimistic work based on this payment now needs to be reversed or reviewed.

The important separation is between seeing a transaction on-chain and making a business promise. The first is a technical observation; the second needs a state machine, not a boolean.

Confirmation thresholds are product policy

Different products tolerate different wait times. A low-value API credit may be usable after fewer confirmations, while a larger subscription, withdrawal limit increase, or trading deposit should wait for stronger assurance. The first question is not “how many blocks does this chain need?” It is “what does it cost us to undo this action if the payment is reversed?”

Use three questions to classify each payment-driven action:

  1. Can the value be revoked, such as a trial, an internal balance, or a coupon?
  2. If the payment is reversed, can the business recover the value, or does it become a real loss?
  3. Can the customer tolerate waiting, or do they need clear progress feedback immediately?

Keep confirmation policy in the platform layer and let business systems consume normalized events. Downstream services should not need to understand block times, RPC lag, and reorg characteristics for every chain; they should handle a stable webhook state machine. See the confirmation model for the current definitions of confirmation and finality.

A common policy split looks like this:

Business scenarioRecommended triggerWhy
Payment-page updates and support notificationsdetectedGives early feedback without changing assets or entitlements
Low-value, reversible internal creditconfirmedBalances experience and risk
Digital goods, account upgrades, and subscription activationfinalizedThe customer should not receive irreversible value from a reversible payment
Physical shipping, withdrawals, and fiat settlementfinalizedLoss and compensation costs are high

This table is a starting point, not the only policy. Higher-risk industries, regulated flows, or unusual accounts can add a risk review on top of the same state machine. Conversely, a small promotional entitlement may be provisioned after confirmed. The important part is that exceptions are explicit policy, not ad hoc conditions scattered across order services.

Map payment events to fulfillment actions

Your order system should retain its own business state, but the payment state should treat the StableOps event stream as the source of truth. Every event should have one clear business action and one clear action that must not happen yet:

EventCustomer-facing stateBackend actionDo not do this yet
payment.detectedPayment received, confirmingRecord the chain transaction and refresh the order pageShip goods or grant irreversible access
payment.confirmedPayment confirmedAllow low-risk fulfillment or enter a risk-review queueAssume the transaction is final
payment.finalizedPayment completeWrite the receipt ledger entry and trigger formal fulfillmentDepend on another client-side poll to decide the outcome
payment.revertedPayment did not completeStop later fulfillment and run compensation or manual reviewSilently ignore optimistic work already performed
payment.expiredOrder expiredClose the receiving flow and guide the customer to create a new orderKeep waiting for the original order

A useful boundary is to use detected to reduce customer anxiety, confirmed for reversible preparation, and finalized for accounting and irreversible fulfillment. With that boundary, delayed or replayed events do not leave the system guessing what each stage is allowed to do.

If the business must query payment status directly, write the result through the same state-transition logic. Webhooks should be the primary path; polling is a recovery and reconciliation fallback, not a separate way to mutate an order.

Webhooks must be idempotent

Confirmation events use at-least-once delivery semantics. Network timeouts, service restarts, and platform replay can deliver the same event more than once. Receivers need to deduplicate with an event ID instead of assuming one delivery per event, and should not rely on an order merely looking successful to skip work.

A reliable handler follows this sequence:

  1. Read the raw request body and verify the signature first.
  2. Create a processing record with a unique constraint on X-Event-Id; a duplicate insert means the event was already handled and can return success.
  3. Move fulfillment, email, and third-party calls onto an internal queue, then return a 2xx response promptly.
  4. Let the background worker run the state transition for the payment event and store its business result alongside the event ID.
import { SIGNATURE_HEADER, verifySignature } from '@stableops/api-sdk/webhooks'

export async function POST(req: Request) {
  const rawBody = await req.text()
  const verification = verifySignature({
    secrets: [process.env.STABLEOPS_WEBHOOK_SECRET!],
    header: req.headers.get(SIGNATURE_HEADER) ?? undefined,
    rawBody,
  })

  if (!verification.ok) return new Response('invalid signature', { status: 400 })

  const eventId = req.headers.get('X-Event-Id')
  if (!eventId || !(await recordEventOnce(eventId))) {
    return new Response('ok')
  }

  await enqueuePaymentEvent(JSON.parse(rawBody))
  return new Response('ok')
}

recordEventOnce can be backed by a database unique index on event_id. The important part is not a particular queue or database: the deduplication record and the resulting side effect must be part of one reliable processing path. Signature verification must use the raw body; parsing and serializing JSON first changes the signature input. See the webhook documentation for signature verification, retries, and replay behavior.

Reversions belong in the state machine

payment.reverted is rare, but it should not be treated as an exception that someone handles after finding it in logs. For every optimistic action that can happen around confirmed, define the compensation in advance: revoke internal credit, cancel a shipping job that has not run, freeze subsequent service, or send the case to manual review.

Compensation does not mean deleting the original business records. Keep the source payment event, the compensating event, and any operator context so finance and support can answer three questions: why the payment was accepted at the time, what the system did, and why it was later reversed. That prevents a chain reorg from being mistaken for an ordinary order cancellation during reconciliation.

Also distinguish between work that has not yet been fulfilled and work that already has. The former can be stopped; the latter may need a reversal, a new payment link, or manual handling depending on the entitlement. Even products that only fulfill after finalized should subscribe to and record payment.reverted, because it can expose mismatches between chain data, node data, and order state.

Launch checklist

  • The payment page clearly distinguishes waiting for payment, confirming, completed, and failed or expired. Do not render every state as success.
  • Only payment.finalized triggers irreversible fulfillment, and the fulfillment action itself can be retried safely by order ID.
  • Webhooks are verified before parsing, deduplicated by X-Event-Id, and return 2xx before the timeout.
  • payment.reverted, payment.expired, and duplicate deliveries have automated coverage or a recorded operational drill.
  • Orders, payment events, transaction hashes, and webhook delivery IDs are linked so support can investigate from an order.
  • Failed deliveries and dead letters are alerted on; replay can restore processing after an endpoint is fixed without manufacturing a successful payment.

Put this checklist into the release process and confirmations become more than an infrastructure setting. They become an auditable product promise.

Next steps

List every action that a received payment can trigger, group those actions into reversible and irreversible categories, then map each group to confirmed or finalized. If you do not yet have end-to-end payment-event tests, start with one idempotent fulfillment flow for payment.finalized and one compensation drill for payment.reverted. Those two paths usually reveal the most important gaps in a payment state design.

Related articles

Compare custodial gateways, direct wallet transfers, and non-custodial payment infrastructure for reliable stablecoin collection.

Create a hosted USDC Checkout Session in Next.js, redirect securely, and fulfill only from verified webhooks.