Verification flow
How the Cloven server verifies an x402 payment. Useful if you're auditing the protocol, building a competing service, or debugging why a payment was rejected.
Fail-closed semantics
x402 verification is strictly fail-closed: any condition that cannot be positively confirmed → 402. There is no "we'll let it through this time." This matters because the failure modes are not symmetric — accepting an invalid payment costs Cloven real revenue and corrupts the trace ledger; rejecting a valid payment is recoverable (the client retries).
The eight verification gates, in order:
1. Header present missing → 402 payment_required
2. Header parseable fail → 400 invalid_payment
3. Required fields fail → 400 invalid_payment
4. Idempotency cache hit hit → accept (Step 8 only)
5. On-chain receipt miss → 402 tx_not_found
6. Status 0x1 fail → 402 tx_reverted
7. USDC.Transfer event matches fail → 402 insufficient_payment | wrong_recipient
8. Age ≤ 10 min fail → 402 tx_too_old
9. Cache + serve — → 200 OKBlock age cap (10 minutes)
The on-chain block.timestamp of the tx must be within 10 minutes of the request's arrival. Older transactions are rejected with 402 tx_too_old.
Why 10 minutes:
- Anti-replay. A leaked
X-Paymentheader is worthless to an attacker after 10 minutes. The valid window is short enough to neutralise log-scrape attacks. - Generous enough for slow RPCs. Base block time is ~2s; even a sluggish RPC + a slow client can comfortably complete sign+broadcast+confirm+retry within 10 minutes.
- Aligns with the 5-minute pack pulse cadence. The data freshness contract is 5 minutes; the payment freshness contract is 2× that — both within the same operational window.
If a client paid > 10 minutes ago, it should treat the payment as lost (or, more accurately, donated to the Cloven treasury — there is no refund). The fix is "re-quote, pay fresh, retry."
Idempotency cache (24h TTL)
A successful verification writes x402:tx:<txHash> to Redis with a 24-hour TTL. Subsequent requests bearing the same X-Payment header hit the cache and skip Steps 5-8 entirely — the response is served as if the tx had just verified.
This means a client can:
- Retry the API call freely for 24 hours with the same payment.
- Recover from network failures between Step 4 and Step 5 by replaying.
- Get "free" reads against the same endpoint within the 24-hour window — yes, this is intentional. A single payment buys one delivery of the response; the client may need to re-fetch if their process crashes.
It also means an attacker who steals an X-Payment header from server logs gets at most 24 hours of free reads against that endpoint (mitigated by the 10-minute on-chain age cap — after 10 minutes the cache is the only path, and after 24 hours nothing works). The threat model is: keep your logs clean.
Event matching
Step 7 requires a USDC.Transfer(from, to, value) event log in the tx receipt where:
from == payer(thepayerfield in theX-Paymentheader).to == X402_RECIPIENT_ADDRESS(Cloven treasury — pinned server-side).value >= quoted amount.- The emitting contract is the canonical Base USDC contract (
0x833589fCD…).
We match against the event log, not the tx value field, because USDC transfers use ERC-20 transfer() — the ETH value of the transaction is always 0. The Transfer event is the real signal.
We tolerate value greater than the quote (overpayment) silently — no refund, but the request goes through. Don't overpay.
RPC redundancy
X402_RPC_URL points at the primary Base RPC provider (Alchemy, Infura, or QuickNode in our setup). If the primary fails Step 5, we fall back to a secondary provider. A request that errors past both providers returns 503 rpc_unavailable rather than 402 — the client should retry the call (not the payment) on backoff.
We do NOT retry verification across providers if any one returns "tx not found" with a high-confidence response — that conclusively means the tx doesn't exist or hasn't propagated yet. Re-quote and re-pay.
Confirmations
Step 5 expects at least 1 confirmation. Base finality is fast enough that 1-conf is the recommended setting. The X402_MIN_CONFIRMATIONS env var allows operators to raise this for higher-value flows, but the default is 1.
Reorgs on Base are extremely rare and the value at stake per call ($0.001–$0.01) does not justify additional confirmation overhead.
What gets logged
Every verification attempt — success or failure — writes a row to the traces table tagged surface: "rest"|"mcp"|"sdk", paid_via: "x402", with the events array recording each gate's outcome:
{
type: "x402_verify",
txHash: "0xabc...",
result: "ok" | "tx_too_old" | "insufficient_payment" | "wrong_recipient" | ...,
payer: "0xWallet...",
amount_paid_usd: 0.001,
ts: "2026-05-24T12:00:00Z"
}Useful for billing reconciliation (run a SUM on amount_paid_usd over your wallet), abuse forensics (multiple tx_too_old from one payer → bot misbehavior), and the Phase 3 Commons revshare ledger.
Discount verification (Phase 3)
When the $CLOVEN token is live, the quote in Step 2 may include a discounted amount. The server records the discount tier in the trace event so the revenue split routes correctly: (quoted_amount × 50%) to buyback-and-burn, etc. The discount itself comes off the infra + team share, not the buyback share — the burn budget is calibrated against the discounted post-tax revenue.
Phase 1 discount lookup returns 0% for every wallet — the code is wired but dormant.