x402 Payment Integration Security Audit — Methodology v1
2026-09-03 · minia2a audit · Audits the resource provider (an agent/platform/gateway selling API services over x402), not the buyer.
🛡️ Run the $5 buyer-side trust check: submit any x402 endpoint to /x402/payment-audit and see whether it resists forged payments, replay, and honeypot payTo — before you pay. Full published standard (v2): github.com/minia2auk/x402-payment-security-checklist.
x402 = HTTP 402 Payment Required. A buyer agent receives a 402 challenge (price/payTo) → pays (USDC) → the resource verifies → delivers. Insecure integrations let a caller steal results without paying (forged payment), consume one payment many times (replay), bypass via race, drain an over-authorized allowance, or lose/lock funds.
We built this checklist from running an x402 gateway in production ourselves — every live lesson below is a bug we hit and fixed on minia2a. If you run an x402 resource, this is the checklist we would apply to you.
1. Payment verification authenticity
Risk: the resource trusts request headers (PAYMENT-SIGNATURE / X-PAYMENT / txHash fields) without actually verifying on-chain settlement → an attacker forges headers and gets results free.
Checks
- Does a payment-header request actually call on-chain/facilitator verification (receipt lookup / /verify), or trust the header?
- Does verification failure refuse delivery (not fall back to "deliver first")?
- Is there a bypass path (different endpoint / header name hitting an unverified branch)?
- What network/chain does verification use — can it be pointed at a testnet / valueless chain?
Live lesson (minia2a): our gateway has two verify paths — Path A (facilitator verify/settle) and Path B (on-chain receipt, traversing transfer logs to confirm USDC reached payTo). Failure returns 402/502, never delivers.
Fix: complete on-chain verification before delivery; verify failure always refuses; one centralized verify function, no bypass.
2. Replay protection (nonce / txHash reuse)
Risk: the same payment (txHash/signature) is reused → pay once, consume N times.
Checks
- Can the same txHash/payment signature call a service repeatedly?
- Is anti-replay persisted (DB/on-chain), or only in memory (lost on restart)?
- Is the replay key scoped globally / per-service / per-wallet in a way that cross-service reuse bypasses?
- After a refund, can the original txHash still be consumed?
Live lesson (minia2a): replay_lock DB table + gateway in-memory cache, key = path+txHash. Pitfall: a test txHash once needed a gateway restart to clear the in-memory cache (DB delete alone wasn't enough) — in-memory replay protection dies on restart, so the DB is the durable line.
Fix: persist replay keys in DB with a unique constraint; scope the key with the service id to stop cross-service reuse; mark a consumed txHash on refund.
3. Delivery timing & race (web↔chain sync)
Risk: delivery-before-verification (concurrent/async path returns results before verification completes → free results); web↔chain desync (web says "paid" but chain hasn't settled, or chain settled but web state is tamperable); same-order concurrent consumption double-spending service resources.
Checks
- Does delivery strictly follow successful verification? Any early-delivery async path?
- Is web-layer payment state user-tamperable (e.g.
order=paid injection)?
- Do concurrent calls on the same payment/order deliver multiple times (no atomic lock)?
- Is server-side billing state (credits/quota) updated atomically (no read-modify-write race)?
Live lesson (minia2a): our "Free Shopping fix" — settle first, then deliver (payment confirm → ledger → then call the service). A liveness probe runs before delivery: if the service is down we refuse to settle (neither collect for nothing nor deliver for free).
Fix: deliver atomically only after verification + ledgering succeed; order state via atomic ops (DB transaction / unique constraint); web-layer state is untrusted — chain is the source of truth.
4. Allowance / permit2 authorization
Risk: an integrator authorizes max uint256 allowance → the payee (or a compromised payee) can drain the full allowance; permit2 approvals with no expiry/scope leave long-term exposure; the payee can change payTo after authorization, redirecting funds.
Checks
- Does integration code request max uint256 allowance? Is it tightened after use?
- Does permit2 authorization have expiry / per-tx cap?
- Can the payee change the receiving address while an authorization is live (payTo mutable without notice)?
- Does a revocation path exist and work?
Live lesson: x402 uses permit2-exact (authorize + single spend). Our challenge carries assetTransferMethod: permit2-exact — single-spend authorization, deliberately not max approval.
Fix: permit2 single-spend (exact), not max; clear/expire after use; payTo changes need governance + notice.
5. Collection & fund safety
Risk: misconfigured payTo (money sent to the wrong address, or self-send rejected); insecure/single-point platform-wallet key; collected funds can't move (no gas / no op access); refund logic exploitable (over/duplicate refunds).
Checks
- Does the challenge's payTo point at the right collector? Can it be injected/tampered?
- Private-key management for the collector (hot/cold separation, permissions)?
- Is there gas on the collection chain to move funds out?
- Can refund conditions be abused (unauthenticated refund trigger / duplicate refund)?
- Does changing payTo require permission (prevents attacker hijacking collection)?
Live lesson (minia2a): we hit self_send_not_allowed — payTo was configured as the same address as the paying wallet and the facilitator rejected all self-sends, taking the platform's payments down. payTo must be an independent collection address ≠ the payer wallet. Our refund path settles on the chain where payment actually occurred (not a handler default), so refunds never land on the wrong chain.
Fix: independent payTo + permissioned changes; layered wallet keys; gas reserve; refunds on the actual payment chain + permission check + duplicate protection.
Severity grading
| Severity | Flaw |
| Critical | Forged payment delivers (1), replay consumes unlimited (2), delivery-before-verification enables free use (3) |
| High | Over-authorization drainable (4), payTo hijackable (5), refund abusable (5) |
| Medium | In-memory-only replay (lost on restart), weak web↔chain state consistency |
How we audit
- White-box: read the resource's gateway/service code, walk it against this checklist.
- Black-box: send crafted requests at x402 endpoints — forged payment headers, reused txHash, concurrent same-order, tampered payTo — and observe whether results are stolen, duplicated, or hijacked.
- On-chain: verify the challenge's payTo, the actual receiving address, and authorization amounts.
Framework v1 — built from operating an x402 gateway plus the x402 spec. The published v2 standard (six categories + liveness pre-filter) lives in the reference repo.