July 13, 2026
Stablecoin payments in production
Confirmations, reorg risk, and why non-custodial design simplifies compliance
Why stablecoins, and why this needs its own design
A creator platform we built accepts subscription and pay-per-view payments in USDT across Ethereum and BNB Chain, through a payment widget users interact with directly from their own wallets. The appeal of a stablecoin here is straightforward: value doesn’t fluctuate against the dollar the way a volatile token would, so pricing a subscription in USDT behaves the same way as pricing it in dollars, without the platform or the creator taking on exchange-rate risk between the moment of payment and the moment of payout. The engineering challenge isn’t the stability of the token — it’s building a payment flow around a settlement layer that doesn’t work like a card network or a bank rail.
Confirmations: how sure is sure enough
A card charge either authorizes or it doesn’t, in real time. A blockchain transaction is different: it’s broadcast, picked up by miners or validators, included in a block, and from that point it’s technically “confirmed” but still has some probability — shrinking with every subsequent block — of being reverted if the chain reorganizes around it. The practical question for any on-chain payment system is: how many confirmations do you wait for before treating the payment as final and releasing whatever it paid for.
The platform waits for 16 confirmations before releasing access to paid content. That number is a deliberate trade-off, not an arbitrary one: fewer confirmations means faster access for the paying user, but a higher probability — however small — of releasing content against a payment that later reorganizes out of the chain; more confirmations means near-certainty at the cost of the user waiting longer after they’ve already paid.
async function checkPaymentStatus(txHash: string, chain: "ethereum" | "bnb") {
const tx = await provider(chain).getTransactionReceipt(txHash);
if (!tx || tx.status !== 1) return { status: "pending" };
const currentBlock = await provider(chain).getBlockNumber();
const confirmations = currentBlock - tx.blockNumber;
if (confirmations >= REQUIRED_CONFIRMATIONS) {
return { status: "confirmed", confirmations };
}
return { status: "confirming", confirmations, required: REQUIRED_CONFIRMATIONS };
}
Reorg risk isn’t hypothetical, it’s a design input
A chain reorganization — where a competing chain of blocks becomes the canonical one and previously “confirmed” transactions in the losing chain are dropped — is rare at depth, which is exactly why waiting for depth matters. Treating a transaction as final at one confirmation and reversing that decision later is a much worse failure mode than making a paying user wait: it means access already granted, or a payout already released, against money that turns out not to have actually settled. The confirmation threshold exists specifically to make that scenario vanishingly unlikely rather than merely unlikely.
Non-custodial design and what it removes
The platform’s payment widget is non-custodial: users pay directly from their own wallets, and the platform never takes custody of user funds before or during the payment. This is a compliance and security decision disguised as a UX one. A custodial design — where users deposit funds into platform-controlled wallets and spend from there — makes the platform a holder of customer funds, with all the regulatory obligations, custody risk, and security surface that implies. Non-custodial design means the platform never holds a balance it’s obligated to safeguard on a user’s behalf in the first place; a payment is a direct, on-chain transfer from the user’s wallet to the payment contract, verified after the fact rather than mediated through a platform-held account.
User wallet --(USDT transfer)--> Payment contract --(event)--> Indexer
↓
Mint/entitlement pipeline
That simplification compounds: less custody means fewer controls to build and audit around fund safekeeping, a smaller set of things that can go wrong on the platform’s side of the transaction, and a clearer story when answering exactly who held funds at any point in the flow — nobody but the user, until the payment settles on-chain.
Dual-rail payouts
Creators are paid out through two separate rails depending on their preference: fiat via Stripe Connect, or crypto directly to a creator-registered wallet address. Both rails consume the same underlying event — a confirmed, entitled payment — and diverge only at the payout step, which keeps the payment-confirmation and entitlement logic identical regardless of how a given creator chooses to get paid.
async function payoutCreator(creatorId: string, amount: Money, entries: LedgerEntry[]) {
const creator = await getCreator(creatorId);
return creator.payoutMethod === "fiat"
? stripeConnect.transfer(creator.stripeAccountId, amount)
: cryptoPayout.send(creator.registeredWalletAddress, amount);
}
What multi-chain adds
Supporting both Ethereum and BNB Chain means the confirmation logic, gas handling, and event indexing all need to be chain-aware rather than hardcoded to one network’s block time and finality characteristics — BNB Chain’s block time and reorg behavior differ from Ethereum’s, so the required confirmation count and the expected wait time are configured per chain rather than assumed to be the same everywhere the platform accepts payment.
What this means for you
Building payments on a settlement layer that confirms probabilistically rather than atomically means picking a confirmation depth deliberately, treating reorg risk as a real input to that decision rather than a theoretical one, and recognizing that non-custodial design isn’t just a user preference — it removes an entire category of custody obligation from the platform’s side of the system. Get the confirmation threshold and the custody model right, and the rest of the payment flow is ordinary event-driven engineering.
30 minutes with a senior engineer.
Tell us what you're building. You'll leave with an honest opinion, even if it's "you don't need us."
Reference calls with past clients are available under NDA during evaluation.