Selling over HTTP 402: our x402 pre-order service
x402 is a protocol that uses the HTTP 402 Payment Required status code to put a price on an API route. A client calls an endpoint, gets a 402 with payment requirements, signs a payment authorization, and retries with an X-PAYMENT header. eval-x402 is our small Bun and Express service that uses this flow to take paid pre-order registrations for ChromBot on Base.
What the service does
The seller server wraps its routes in paymentMiddleware from x402-express. Each route declares a USD price, the network (base), and a description, plus input and output schemas so the route is marked discoverable and a client or agent can see what fields it needs before paying.
There are four priced routes:
POST /chrombot-super-early-bird, the only tier that takes registrations today. It needs an email and a Telegram handle in the JSON body.POST /chrombot-early-birdandPOST /chrombot-public-sale, which are placeholders that answer "coming soon".GET /ping, a minimal paid route used to test the payment loop end to end.
Checks after payment
Once the middleware has accepted a payment, the handler for the open tier still does its own checks:
- It counts existing registrations for the tier and refuses once the unit cap is reached.
- It decodes the
X-PAYMENTheader withexact.evm.decodePaymentand reads the payer address from the signed authorization. - It rejects a wallet that has already registered, so each address gets one unit.
- It validates the email and Telegram fields, then writes the row to a local SQLite table (
bun:sqlite) inside a transaction that re-checks for duplicates.
A self-hosted facilitator
In x402, a facilitator is the service that verifies a signed payment and settles it on chain. The repo includes its own Facilitator class built on the x402/facilitator verify and settle functions. It exposes three endpoints, GET /supported, POST /verify and POST /settle, through a framework-agnostic handleRequest method and a small Express adapter:
1const facilitator = new Facilitator({2 evmPrivateKey: process.env.EVM_PRIVATE_KEY!,3 networks: [base],4});5createExpressAdapter(facilitator, app, "/facilitator");
It checks that the requested network is one it was configured for and one x402 supports before it verifies or settles anything. In the committed version the seller is wired to Coinbase's hosted facilitator from @coinbase/x402, and the local facilitator runs alongside it on a separate port. Switching between them is a one-line change in the middleware config.
Buyer scripts
script/buyer.ts shows the client side: it wraps fetch with wrapFetchWithPayment from x402-fetch, so the 402 challenge, signing and retry happen automatically, then decodes the x-payment-response header to read the settlement result.
Why we built it
We wanted a working example of selling something to both people and agents over plain HTTP, with no checkout page and no account system. A paid route with a published schema is enough for an agent to discover the offer, pay on Base, and get a confirmation back in one request cycle.
