TL;DR: Flashblocks let us return an x402 settlement result on Base in about 750 ms p50, instead of 1.7 s. But a preconfirmation is not finality. We pair optimistic responses with canonicality checks, payment-log validation, reconciliation, and automatic resubmission of still-valid signed authorizations.
Base produces a block every 2 seconds. Flashblocks — the preconfirmation stream Base shipped in 2025 — commits transactions every ~200 milliseconds. For an x402 facilitator, that gap is the whole game: it's the difference between telling a merchant "you got paid" in two-plus seconds and telling them in well under one.
But a preconfirmation is a promise, not a fact. It comes from the sequencer before consensus, and in rare cases — a sequencer restart, a failover, a reorg — a transaction that was preconfirmed never makes it into the sealed block.
A facilitator that naively returns success on a preconfirmation has, in that moment, minted a payment confirmation for a transaction that never settled.
This post covers both halves of the work we shipped in PayAI's x402 facilitator: how we cut Base settlement latency by more than half using Flashblocks, and the finality engineering — sealing proofs, reconciliation, automated resubmission — that makes it safe.
What "settling" on Base actually waits for
When a merchant's server accepts an x402 payment, the buyer doesn't send a transaction — they send a signed authorization: an EIP-3009 transferWithAuthorization for USDC, signed off-chain. If you haven't seen the HTTP 402 Payment Required flow end to end, that's the shape of it: the 402 response carries the terms, the signature carries the payment.
The facilitator verifies it, submits it on-chain from its own wallets, waits for evidence the transfer landed, and returns the settlement result. The merchant releases the resource on that response — you can watch that exact handshake run against a live x402 endpoint.
So settlement latency is dominated by one question: how quickly can you obtain trustworthy evidence that the transfer executed? On Base, the naive answer is "wait for a receipt in a sealed block" — which means you inherit the 2-second block cadence, plus however sloppily you poll for it.
We were sloppier than we knew. Measuring our own production endpoint from the outside — real settlements, warm connections, network overhead subtracted — Base settlement sat around 1.7 seconds p50. Three separate taxes stacked on top of the chain:
- A batching delay that batched nothing. Settlements queue through a Multicall3 batching layer with a flush timer that waited 750ms to fill a batch. Under load, batching is a big win. At off-peak volume, the window captured a second settlement well under 1% of the time — meaning nearly every settlement paid 750ms to save nothing. Idle lanes now flush immediately; batching still engages under bursts via the size threshold and natural coalescing while a flush is in flight.
- Receipt discovery gated on block arrival. Our receipt wait used a standard library helper (viem) that checks for the receipt once, then re-checks only when the chain's block number changes. That's a sensible default for a wallet UI. For a facilitator, it structurally locks receipt discovery to the 2-second block cadence — no matter how fast your RPC is.
- A slow poll on top. Where polling did happen, it happened on a half-second interval, quantizing everything it touched.
None of these required Flashblocks to fix. Clearing the first two brought Base settlement to roughly 1.1 seconds p50. The third only pays off once there's something faster to poll for — which is where Flashblocks come in.
What Flashblocks change
Flashblocks are partial-block commitments streamed by the Base sequencer roughly every 200ms.
Flashblocks-aware RPC providers expose them in two useful ways:
eth_getTransactionReceiptreturns a preconfirmation receipt for a transaction that's in a flashblock but not yet in a sealed block;- a WebSocket stream publishes flashblock contents in real time.
We initially built for both, then opted for receipts only.
The receipt path was a straight win: direct eth_getTransactionReceipt calls on a 125ms cadence, accepting preconfirmation receipts, dropped Base settlement p50 to ~750ms net of network overhead — the receipt was already in hand at response time on every settlement we sampled, and most of those were still preconfirmations at the moment we answered. The response beats the block.

The WebSocket path we built under a design rule we'd still recommend to anyone working with preconfirmations: the stream is a trigger, never the truth. A subscription watches for our transaction hashes and wakes the receipt poller the moment one appears; the poller remains the source of truth and the complete fallback, so a socket that drops, lags, or lies degrades to exactly the behavior we'd have without it.
Then we A/B'd it against real settlements in staging, and it lost — twice over.
It is slower than the endpoint it was meant to accelerate. The wake path demonstrably works: 7 of 10 settlements were woken by the WebSocket, so the subscription really does deliver our hashes. But the flashblock notification arrived at ~390ms median, while eth_getTransactionReceipt already served the preconfirmation receipt at ~150ms. The provider's receipt endpoint is faster than the provider's own flashblock stream. The stream can only "win" if you handicap the poller — and when we did exactly that to test it (400ms interval), settle p50 went from 592ms to 814ms.
So: go with standard transaction receipts; but we recommend testing both to see how you fare.
The part you don't get to skip: preconfirmations can be reorged
Here is one uncomfortable truth: in the window between a flashblock and its sealed block, the merchant may have already released the resource for a payment that can still disappear.
If the sequencer restarts or fails over, a preconfirmed transaction can be dropped.
Usually it just gets re-included a block or two later — the transaction still exists
and re-enters from the mempool. But "usually" is not a settlement guarantee, and a payments system doesn't get to hand-wave its worst case.
We shipped the mitigation as four layers. Each exists because a subtler version of the problem survived the previous one.

Layer 1 — prove canonicality, don't infer it.
How do you even know a receipt is preconfirmed versus sealed?
The common convention is an all-zero blockHash on preconfirmation receipts — but that's a provider convention, not a standard, and any logic keyed on it silently misclassifies on providers that use non-zero placeholders. PayAI Network runs across multiple chains, and provider conventions differ on every one of them — which is why we don't key on any of them.
RPCs happily serve orphaned blocks by hash — that's how you inspect
reorged history — so an already-reorged receipt would pass.
The correct check is at height: fetch the canonical block at the receipt's blockNumber and require its hash to equal the receipt's blockHash.
Layer 2 — sealed still isn't paid.
We batch settlements through Multicall3 with allowFailure, so the outer transaction can succeed while one inner transfer reverts.
A sealed receipt with status: success therefore proves nothing about a specific payment. The only ground truth is the AuthorizationUsed event for that specific authorization, re-parsed from the canonical receipt — which, after a reorg, can differ from the preconfirmation receipt you originally accepted.
Layer 3 — reconcile everything you couldn't prove.
Any settlement whose receipt wasn't provably canonical at response time gets scheduled for asynchronous reconciliation — off the response path, so it costs the merchant nothing.
Reconciliation re-fetches until the transaction seals canonically, then re-runs the sealed receipt through the same log-parsing path the hot path uses, atomically replacing the cached outcome and authorization set.
It ends in one of four states, each a metric you can alert on: sealed-and-paid (the overwhelming norm), sealed-but-reverted, sealed-with-your-authorization-missing (the Multicall3 case), and never-sealed-by-deadline.
Layer 4 — recover using the thing the buyer actually gave you.
This is the piece that makes the design click.
The buyer's payment was never a transaction — it was a signed authorization that remains valid until its expiry, regardless of what happened to any transaction carrying it.
So when reconciliation lands on "never sealed" or "authorization missing," the facilitator doesn't need the buyer back. It checks the authorization is still
unexpired and then re-enqueues it through the normal settlement pipeline.
For merchants that want no optimistic window at all, a strict mode waits for canonically sealed receipts — a few hundred milliseconds slower, still far faster than where we started. Both modes are available from the same integration; the x402 quickstart documentation covers the setup.
Where this lands
| Base settlement (p50, measured externally, net of network) | |
|---|---|
| before | ~1.7s |
| flush idle batches immediately + direct receipt polling, preconfirmations accepted | ~1.1s |
| polling interval tightened 500ms → 125ms | ~750ms |
| worst-case safety | reconciliation + automated resubmission on a still-valid authorization |
The takeaways we'd offer anyone building payments on an L2 with preconfirmations:
- Fast and final are separate features. Optimistic acceptance is a product decision; canonical verification is an engineering obligation; automated recovery is what makes the two compatible. If you can't state your reorg story, your fastest path is a liability with good latency.
- Triggers, not truth — and check you need the trigger at all. Streams and preconfirmation channels should wake your verification logic, never replace it, so that a lying or dead stream degrades to your slow path. Then A/B it before you ship it: the "obviously faster" push channel was slower than the pull endpoint it was accelerating, and billed per event on a firehose we didn't need. A boring poll on a tight interval is a strong baseline.
- Prove canonicality at height, and validate payments from logs. Zero-hash markers are conventions; blocks fetched by hash may be orphans;
status: successis not your payment. - When the payment primitive is a signature, hold onto it. The authorization outliving the transaction is what turns a reorg from an incident into a retry.
The facilitator, the settlement pipeline and the reconciliation logic are open — PayAI on GitHub if you want to read the implementation rather than the description.
Numbers are p50s from our external benchmark harness against the production facilitator
