Take a 402 to a paid 200 without real money.
Tollgate is a local mock server and CLI for the HTTP 402 payment handshake. Stand up a fake paywall, drive the challenge to settle to retry loop, and script it as a CI check, before you touch a wallet or a settlement network.
$ paywall-sandbox request \ --url http://localhost:8402/paid --verbose --- initial request --- GET /paid -> 402 Payment Required --- 402 challenge received --- descriptor: {Amount:100 Asset:USDC Recipient:0xsandbox Nonce:9f1c... } --- retry with proof --- proof: {Nonce:9f1c... Scheme:fake Signature:ok} GET /paid -> 200 OK
What it does
Everything the handshake needs, nothing that costs money.
A mock 402 origin
Challenge any route with a real payment descriptor: amount, asset, recipient, nonce, and expiry. Configure routes from flags or a JSON rule set, with exact-path or /* prefix matching.
A client that plays both sides
Run the full challenge to pay to retry loop against any target, mock or real. --verbose traces every header, descriptor field, and proof, so you see exactly what a real client sends.
Scenarios as CI checks
Write a JSON scenario of expected challenges and outcomes. test runs it against an in-process server and exits non-zero on any mismatch, so a broken paywall fails the build.
Quick start
Two commands to a settled request.
1. Stand up a paywall
Build the binary and serve one protected route.
$ go build -o bin/paywall-sandbox ./cmd/paywall-sandbox $ ./bin/paywall-sandbox serve \ --path /paid --amount 100 --asset USDC paywall-sandbox listening on :8402 (1 rule(s))
2. Drive the loop
Point the client at it and watch the 402 settle to a 200.
$ ./bin/paywall-sandbox request \
--url http://localhost:8402/paid
GET http://localhost:8402/paid -> 200
Swap --scheme hmac-sha256 --hmac-key <secret> to settle with a shared-secret signature instead of the unconditional fake scheme.
Background
Why a sandbox for HTTP 402?
HTTP 402 Payment Required sat unused in the spec for three decades. A recent wave of pay-per-call APIs, led by the x402 pattern, is finally giving it a concrete shape: a server answers a request with 402 and a payment descriptor, the client (or an AI agent acting for it) settles payment out of band, then retries the request with proof attached. The idea is clean, but there is no dominant SDK yet, and the wire format is still being pieced together from scattered specs and reference code.
That leaves a gap. If you are building the server side, you want to check that your descriptor is well formed, your nonces do not leak, and expired challenges are rejected. If you are building the client or agent side, you want to confirm you parse the challenge, attach a valid proof, and retry correctly. Doing either against real settlement means a wallet, a network, live funds, and no easy way to script the edge cases: a replayed proof, a challenge that expired one instant ago, a malformed descriptor from a hostile origin.
Tollgate removes real money from that loop. The mock server issues genuine 402 challenges with configurable terms and consumes each proof exactly once, so replays fail the way they should. The client drives the whole exchange and prints every step. Scenarios pin the expected behavior in a JSON file that runs in CI. You develop against the exact shape of the protocol, then point the same client at your real origin when settlement is wired up. It is a single static Go binary with no runtime dependencies, so it drops into a container or a CI job without setup.
FAQ
Does Tollgate move real money or connect to a blockchain?
fake scheme accepts any proof once the nonce and expiry check out, and hmac-sha256 verifies a shared-secret signature. Both simulate settlement so you can exercise the handshake without a wallet, a network, or funds.What is the difference between HTTP 402 and x402?
Can I test my own client against it, not just the bundled one?
serve and point any HTTP client at it. The challenge lives in the X-Payment-Required header and body; the client returns proof in the X-Payment header on retry. The full wire format is in docs/PROTOCOL.md.How do I use it as a CI check?
paywall-sandbox test scenario.json. It starts and tears down its own server and exits non-zero on any failed assertion, so it works as a build step with no service left running.