Tollgate
View on GitHub
HTTP 402 · x402 pay-per-request

Take a to a paid 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.

Everything the handshake needs, nothing that costs money.

serve

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.

request

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.

test

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.

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.

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?
No. It is entirely offline. The 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?
HTTP 402 is the status code reserved in the HTTP spec for "Payment Required." x402 is a modern convention for using it: a 402 response carries a payment descriptor, the client settles out of band and retries with proof. Tollgate models this challenge to pay to retry loop so you can test against it directly.
Can I test my own client against it, not just the bundled one?
Yes. Run 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?
Write a scenario JSON describing the requests and expected outcomes, then run 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.
What do I need to run it?
Go 1.22 or newer to build from source, or a prebuilt binary from the releases page. It is a single static binary with no runtime dependencies.