Confirmations
Understanding blockchain confirmations and the four-stage state machine.
Blockchain confirmations are the process by which transactions become increasingly secure and irreversible. StableOps tracks payments through four distinct confirmation stages to balance speed with security.
Overview
When a payment is sent on a blockchain, it goes through multiple stages before it's considered final:
DETECTED → CONFIRMED → FINALIZED
↓ ↓
REVERTED REVERTEDEach stage represents a different level of confidence that the payment is permanent:
- DETECTED: Transaction seen on blockchain (0 confirmations)
- CONFIRMED: Transaction has required confirmations (chain-specific)
- FINALIZED: Transaction is irreversible (finality reached)
- REVERTED: Transaction was reversed due to reorg or failure
Why Confirmations Matter
Blockchains are distributed systems where multiple nodes compete to add blocks. This can lead to:
- Blockchain reorganizations (reorgs): A competing chain becomes longer, reversing recent blocks
- Transaction failures: Smart contract execution fails or runs out of gas
- Double-spend attempts: Malicious actors try to reverse transactions
Confirmations protect against these risks by waiting for the transaction to be buried deep enough in the blockchain that reversal becomes computationally infeasible.
Confirmation Stages
DETECTED (0 Confirmations)
The transaction has been broadcast and seen in the mempool or included in the latest block.
Confidence Level: Low (5-10%)
Risk:
- Transaction could be replaced (if using RBF)
- Block could be orphaned in a reorg
- Transaction could fail execution
When to Use:
- Show "Payment received, confirming..." in UI
- Update order status to "pending"
- Do not fulfill the order yet
Example:
// Webhook: payment.detected
{
"type": "payment.detected",
"data": {
"payment_order_id": "po_abc123",
"from_status": "created",
"to_status": "detected",
"reason": "deposit_detected",
"normalized_event_id": "evt_abc123",
"amount": "10.00",
"settlement_asset": "USDC",
"metadata": {}
}
}CONFIRMED (Chain-Specific Confirmations)
The transaction has received the required number of confirmations for the specific blockchain.
Confidence Level: High (95-99%)
Confirmation Requirements by Chain:
| Chain | Confirmations | Time | Rationale |
|---|---|---|---|
| Base | 2 blocks | ~4 seconds | Optimistic rollup, low reorg risk |
| Ethereum | 6 blocks | ~1.2 minutes | Standard for exchanges |
| Arbitrum | 1 block | ~0.3 seconds | Optimistic rollup |
| Polygon | 20 blocks | ~40 seconds | Higher reorg risk |
| TRON | 1 block | ~3 seconds | DPOS consensus |
Risk:
- Deep reorgs are still theoretically possible
- Very rare in practice (< 0.01% of transactions)
When to Use:
- Low-value transactions (< $100)
- Digital goods delivery
- Account credits
- Time-sensitive fulfillment
Example:
// Webhook: payment.confirmed
{
"type": "payment.confirmed",
"data": {
"payment_order_id": "po_abc123",
"from_status": "detected",
"to_status": "confirmed",
"reason": "confirmations_reached",
"normalized_event_id": "evt_abc123",
"amount": "10.00",
"settlement_asset": "USDC",
"metadata": {}
}
}FINALIZED (Finality Reached)
The transaction has reached finality and cannot be reversed under any circumstances.
Confidence Level: Absolute (100%)
Finality Requirements by Chain:
| Chain | Finality | Time | Mechanism |
|---|---|---|---|
| Base | 18 blocks | ~36 seconds | L1 finality |
| Ethereum | 64 blocks | ~13 minutes | Casper FFG |
| Arbitrum | 20 blocks | ~5 seconds | L1 finality |
| Polygon | 128 blocks | ~4.3 minutes | Checkpoint finality |
| TRON | 19 blocks | ~1 minute | DPOS finality |
Risk: None - payment is guaranteed
When to Use:
- High-value transactions (> $100)
- Physical goods shipment
- Irreversible actions (account upgrades, subscriptions)
- Recommended for all production fulfillment
Example:
// Webhook: payment.finalized
{
"type": "payment.finalized",
"data": {
"payment_order_id": "po_abc123",
"from_status": "confirmed",
"to_status": "finalized",
"reason": "finality_reached",
"normalized_event_id": "evt_abc123",
"amount": "10.00",
"settlement_asset": "USDC",
"metadata": {}
}
}REVERTED (Transaction Reversed)
The transaction was reversed due to a blockchain reorganization or execution failure.
Causes:
- Blockchain reorg: A competing chain became longer
- Transaction failure: Smart contract execution failed
- Insufficient gas: Transaction ran out of gas
- Receipt not found: Transaction disappeared from blockchain
Frequency: Very rare (< 0.01% of confirmed transactions)
When This Happens:
- StableOps detects the reorg or failure
- Order status changes to
REVERTED payment.revertedwebhook is sent- Address is released back to the pool (becomes
AVAILABLEfor reuse)
Example:
// Webhook: payment.reverted
{
"type": "payment.reverted",
"data": {
"payment_order_id": "po_abc123",
"from_status": "confirmed",
"to_status": "reverted",
"reason": "blockchain_reorg",
"normalized_event_id": "evt_abc123",
"amount": "10.00",
"settlement_asset": "USDC",
"metadata": {}
}
}How to Handle:
app.post('/webhooks/stableops', async (req, res) => {
const event = req.body
if (event.type === 'payment.reverted') {
const orderId = event.data.payment_order_id
// 1. Reverse any fulfillment (if already done)
await reverseOrderFulfillment(orderId)
// 2. Notify customer
await sendEmail(customer, 'Payment failed, please try again')
// 3. Update your database
await db.orders.update({
id: orderId,
status: 'payment_failed',
reason: event.data.reason
})
// 4. Create new payment order (optional)
const newOrder = await stableops.paymentOrders.create({
merchantOrderId: `${orderId}_retry`,
amount: originalAmount,
// ...
})
}
res.sendStatus(200)
})Choosing the Right Confirmation Level
Decision Matrix
| Transaction Value | Fulfillment Type | Recommended Stage | Rationale |
|---|---|---|---|
| < $10 | Digital goods | CONFIRMED | Fast, low risk |
| $10 - $100 | Digital goods | CONFIRMED | Balanced speed/security |
| $100 - $1,000 | Digital goods | FINALIZED | Higher security needed |
| > $1,000 | Any | FINALIZED | Maximum security |
| Any | Physical goods | FINALIZED | Irreversible shipping |
| Any | Account upgrades | FINALIZED | Irreversible changes |
| Any | Subscriptions | FINALIZED | Recurring billing |
Risk Tolerance
Low Risk Tolerance (Financial services, high-value goods):
- Always wait for FINALIZED
- Never fulfill on DETECTED
- Consider additional fraud checks
Medium Risk Tolerance (E-commerce, SaaS):
- FINALIZED for > $100
- CONFIRMED for < $100
- DETECTED only for UI updates
High Risk Tolerance (Gaming, low-value digital goods):
- CONFIRMED for most transactions
- FINALIZED for account changes
- Accept occasional reversals
Monitoring Confirmations
Real-Time Updates
StableOps continuously monitors the blockchain and sends webhooks as confirmations increase:
// Timeline of webhooks for a single payment
12:00:00 - payment.detected
12:00:12 - payment.confirmed
12:13:00 - payment.finalizedPolling Alternative
If you prefer polling over webhooks:
const checkPaymentStatus = async (orderId: string) => {
const order = await stableops.paymentOrders.retrieve(orderId)
switch (order.status) {
case 'detected':
console.log('Payment seen, waiting for confirmations...')
break
case 'confirmed':
console.log('Payment confirmed, waiting for finality...')
break
case 'finalized':
console.log('Payment finalized, safe to fulfill!')
await fulfillOrder(order)
break
case 'reverted':
console.log('Payment reverted, handle failure')
await handleRevert(order)
break
}
}
// Poll every 5 seconds
const interval = setInterval(() => checkPaymentStatus(orderId), 5000)Blockchain Reorganizations
What is a Reorg?
A blockchain reorganization occurs when a competing chain becomes longer than the current chain, causing recent blocks to be replaced.
Before Reorg:
Block 100 → Block 101 → Block 102 (your transaction)
↘ Block 102' (competing chain)
After Reorg:
Block 100 → Block 101 → Block 102' → Block 103'
✗ Block 102 (orphaned)Reorg Frequency by Chain
| Chain | Reorg Depth | Frequency | Notes |
|---|---|---|---|
| Ethereum | 1-2 blocks | Daily | Usually harmless |
| Ethereum | > 3 blocks | Rare | Requires investigation |
| Base | 1 block | Occasional | L2 reorgs are rare |
| Polygon | 1-5 blocks | Common | Higher reorg risk |
| TRON | 1 block | Rare | DPOS consensus |
How StableOps Handles Reorgs
- Continuous Monitoring: StableOps checks
blockHashon every confirmation - Reorg Detection: If
blockHashchanges, a reorg is detected - Status Update: Order status changes to
REVERTED - Webhook Notification:
payment.revertedwebhook is sent - Address Release: Address is released back to the pool (
AVAILABLE)
Reorg Protection
StableOps implements multiple layers of reorg protection:
- Block hash verification: Compare stored
blockHashwith current chain - Receipt validation: Verify transaction receipt still exists
- Confirmation counting: Only count blocks on the canonical chain
- Finality tracking: Wait for chain-specific finality guarantees
Best Practices
1. Always Wait for FINALIZED
// ✅ Good - wait for finality
app.post('/webhooks/stableops', async (req, res) => {
const event = req.body
if (event.type === 'payment.finalized') {
await fulfillOrder(event.data.payment_order_id)
}
res.sendStatus(200)
})
// ❌ Bad - fulfill on detection
app.post('/webhooks/stableops', async (req, res) => {
const event = req.body
if (event.type === 'payment.detected') {
await fulfillOrder(event.data.payment_order_id) // Risky!
}
res.sendStatus(200)
})2. Handle All States
const handlePaymentWebhook = async (event: WebhookEvent) => {
switch (event.type) {
case 'payment.detected':
await updateUI('Payment received, confirming...')
break
case 'payment.confirmed':
await updateUI('Payment confirmed, finalizing...')
break
case 'payment.finalized':
await fulfillOrder(event.data.payment_order_id)
break
case 'payment.reverted':
await handleRevert(event.data.payment_order_id)
break
}
}3. Show Progress to Users
// Update UI based on confirmation stage
const PaymentStatus = ({ order }) => {
switch (order.status) {
case 'detected':
return (
<div>
<Spinner />
<p>Payment received, confirming...</p>
</div>
)
case 'confirmed':
return (
<div>
<Spinner />
<p>Payment confirmed, finalizing...</p>
</div>
)
case 'finalized':
return (
<div>
<CheckIcon />
<p>Payment complete! Your order is being processed.</p>
</div>
)
}
}4. Log Everything
// Log all confirmation events for debugging
app.post('/webhooks/stableops', async (req, res) => {
const event = req.body
await db.webhookLogs.create({
type: event.type,
orderId: event.data.payment_order_id,
fromStatus: event.data.from_status,
toStatus: event.data.to_status,
timestamp: new Date(),
})
// Process event...
res.sendStatus(200)
})5. Test Reorg Scenarios
// Simulate reorg handling in tests
describe('Payment reorg handling', () => {
it('should reverse fulfillment on reorg', async () => {
// 1. Create order
const order = await createOrder()
// 2. Simulate payment detected
await handleWebhook({ type: 'payment.detected', data: order })
// 3. Simulate payment confirmed
await handleWebhook({ type: 'payment.confirmed', data: order })
// 4. Simulate reorg
await handleWebhook({ type: 'payment.reverted', data: order })
// 5. Verify fulfillment was reversed
const dbOrder = await db.orders.findOne({ id: order.id })
expect(dbOrder.status).toBe('payment_failed')
})
})Common Patterns
Progressive Fulfillment
Fulfill parts of the order at different confirmation stages:
app.post('/webhooks/stableops', async (req, res) => {
const event = req.body
const orderId = event.data.payment_order_id
switch (event.type) {
case 'payment.confirmed':
// Grant temporary access
await grantTrialAccess(orderId)
break
case 'payment.finalized':
// Upgrade to full access
await grantFullAccess(orderId)
break
case 'payment.reverted':
// Revoke all access
await revokeAccess(orderId)
break
}
res.sendStatus(200)
})Conditional Fulfillment
Choose confirmation level based on transaction value:
const shouldFulfill = (order: PaymentOrder): boolean => {
const amount = parseFloat(order.amount)
if (amount < 100) {
// Low value: fulfill on confirmed
return order.status === 'confirmed' || order.status === 'finalized'
} else {
// High value: wait for finalized
return order.status === 'finalized'
}
}Next Steps
- Payment Orders - Understanding payment orders
- Webhooks - Receiving real-time notifications
- Integration Guides - Framework-specific examples