Adding payment to an endpoint raises a question you don't have with a normal API: how do you test it without moving real money? A 402 challenge, a payment, a verify step, and a retry all have to line up — and the caller is often an autonomous agent that will hammer the path in ways a human never would. You want to exercise the whole loop, including the failures, before a single real cent moves. This post is the workflow for doing that.
How do you test x402 payments? Point your integration at a test environment that settles payments synthetically (no real funds), drive the full request → 402 → pay → verify → 200 loop from a test client, assert the failure modes (unpaid, replayed, expired), and only then flip one environment variable to go live. With a synthetic sandbox you need no testnet, no faucet, and no wallet to do it.
Why x402 needs its own testing approach
A normal API test asserts status codes and bodies. A paid endpoint adds three things that are easy to get wrong and expensive to get wrong in production:
- Money moves per call. Every real request either settles or it doesn't. A bug that serves paid content for free, or double-charges, is a revenue or trust problem — not a failed assertion.
- The caller is autonomous. An agent retries, parallelizes, and reuses responses. Your gate has to be correct under replay and concurrency, not just on the happy path.
- Failure has to be safe. When verification can't complete, the endpoint must fail closed — never serve the paid response. That's a behaviour you must test, not assume.
So the goal of an x402 test suite isn't "did I get a 200." It's "does the gate charge exactly once, serve only after payment, and refuse in every failure case" — asserted automatically, repeatedly, without spending anything.
Two ways to test x402
There are two distinct test substrates, and they answer different questions.
1. Testnet (for raw x402 / on-chain settlement)
If you're integrating x402 directly against an on-chain facilitator, you test on a testnet: a real blockchain with worthless money. For EVM that's typically Base Sepolia (chain ID 84532). You fund a test wallet from a faucet — Circle's USDC faucet dispenses test USDC on Base Sepolia — point your route at a testnet facilitator (the community x402.org facilitator is free for Base Sepolia and Solana devnet), and run the flow with network: "base-sepolia". Some sandboxes even offer "magic" test addresses so you can skip the faucet entirely.
This exercises the actual settlement path — signatures, chain confirmation, the works. The cost is setup: wallets, faucets, RPC endpoints, testnet flakiness, and confirmation latency in your test loop.
2. Synthetic sandbox (no chain, no faucet)
The second substrate skips the chain. A synthetic sandbox accepts test payment proof and settles it inside the platform without touching a blockchain, so the full challenge → pay → verify → allow loop runs deterministically and instantly. This is how Paywall's test mode works: set X402_ENV=sandbox, and the same code that will handle real USDC in production handles synthetic settlement in test — no wallet, no faucet, no confirmation wait. Because the X402v1 wire contract is frozen and byte-identical across environments, a loop that passes in the sandbox behaves the same on live; the only thing that changes is whether settlement is real.
| Testnet (Base Sepolia) | Synthetic sandbox | |
|---|---|---|
| Real chain | Yes (test money) | No |
| Needs a wallet + faucet | Yes | No |
| Deterministic in CI | Flaky (confirmations, faucets) | Yes |
| Tests on-chain settlement | Yes | No (synthetic) |
| Speed per call | Seconds (block time) | Instant |
| Best for | Verifying raw on-chain flow | Fast, repeatable integration + CI tests |
Most teams shipping on a managed x402 platform want the second: the platform owns settlement, so your job is to prove your integration is correct, and a synthetic sandbox does that faster and without flakiness. Teams hand-rolling on-chain x402 will want the testnet to validate settlement itself.
The sandbox workflow, step by step
Against a synthetic sandbox, a complete test pass looks like this:
- Configure the test environment. Set your sandbox credentials and
X402_ENV=sandbox. The environment variables are the same ones you'll use live —X402_API_KEY,X402_SECRET,X402_ENV,X402_BASE_URL— so nothing about your code path is test-only. - Assert the unpaid request is refused. Call the metered route with no payment. Expect
402 Payment Requiredwith a challenge that quotes the price, currency, and a nonce. Assert the price is what you configured. - Drive the payment and retry. Have the test client settle the challenge synthetically and retry with the proof and the nonce. Expect
200and the real body. This is the happy path, proven without money. - Assert the failure modes (below). This is the part teams skip and shouldn't.
- Run it in CI. Because the sandbox is deterministic and instant, the whole suite runs on every pull request — no external funding, no waiting on block confirmations.
The exact test-client snippet depends on your stack; the SDK pages carry the per-language version, and all of them speak the same wire contract, so a test written against one behaves identically against the others.
The failure modes worth testing
The happy path is the easy 20%. The value is in asserting that the gate refuses correctly. An x402 gate rejects with a precise reason, and each is a test case:
| You send… | Expect | Reason |
|---|---|---|
| A request with no payment | 402 |
unpaid |
| A retry with a wrong/missing nonce | 402 |
bad_nonce |
| A replayed nonce or proof | 402 |
replay |
| A challenge used after it expired | 402 |
expired |
| A request while the platform is unreachable | 502 |
fail-closed — paid content is never served |
Two of these matter more than the rest. Replay — reusing a payment proof to get a second free response — is the attack an autonomous caller can stumble into by simply retrying; assert that the second use is rejected. And the fail-closed case: simulate the platform being unreachable and assert your route returns a 502 and never the paid body. That single test is the difference between "safe by design" and "gave away paid content during an outage." (For where these reasons come from on the wire, see the technical deep dive.)
From sandbox to live
The point of a synthetic sandbox is that going live changes as little as possible. With Paywall it's one variable: flip X402_ENV from sandbox to live, and the same code stops accepting synthetic settlement and starts accepting real USDC. No rewrite, no second integration, no protocol change — the wire contract is identical.
A sane rollout:
- Green suite in sandbox. Happy path plus every failure mode above, passing in CI.
- One live smoke test. Flip to live in a controlled environment and run a single real, low-value call end to end to confirm credentials and payout wiring.
- Watch the first real payments in the dashboard, then widen the pricing rules.
Because test and live share the frozen wire contract, the sandbox suite you wrote in step 1 keeps its value after launch — it's a regression net, not throwaway scaffolding.
FAQ
Do I need a testnet to test x402?
Not with a synthetic sandbox. A sandbox settles payments synthetically, so you can run the full challenge → pay → verify → allow loop with no blockchain, wallet, or faucet. You only need a testnet (like Base Sepolia) if you're validating raw on-chain settlement yourself.
How do I get test USDC on Base Sepolia? Request it from a testnet faucet — Circle's faucet dispenses test USDC on Base Sepolia. This only applies to the on-chain testnet path, not to a synthetic sandbox.
Can I run x402 tests in CI? Yes — a synthetic sandbox is deterministic and instant, so the full payment loop runs on every pull request with no external funding. Testnet-based tests can run in CI too, but faucets and block confirmations make them slower and flakier.
What's the difference between sandbox and live?
In sandbox, settlement is synthetic (no real funds); in live, payments settle in real USDC. With Paywall the switch is a single environment variable (X402_ENV), and the wire contract is identical, so behaviour matches.
What should an x402 test suite assert?
The happy path (unpaid → 402, paid → 200) plus every failure mode: unpaid, bad_nonce, replay, expired, and fail-closed (502, never serving paid content when the platform is unreachable).
Next steps
- Wire up the integration first: x402 developer integration guide
- The status code underneath it all: HTTP 402 Payment Required
- The client's view of the loop: how AI agents pay for APIs
- Pick your stack: the nine SDKs · protocol detail in the technical deep dive
- Try the full loop in test mode: create an account