How x402 payments work
x402 brings back the long-unused HTTP 402 Payment Required status code. The server states a price, the client attaches a signed payment, and a facilitator verifies and settles it on-chain. No accounts, invoices or card networks are involved.
The four messages
- 01client → tinyx402GET /api/v1/search?query=…No payment attached.
- 02tinyx402 → client402 + PAYMENT-REQUIREDPrice, asset, network, payTo.
- 03client sign EIP-3009 authorizationOff-chain. No gas, no broadcast.
- 04client → tinyx402GET … + PAYMENT-SIGNATURE
- 05tinyx402 → facilitator/verifySignature, balance, amount, expiry.
- 06tinyx402 run the searchIf this fails, stop here: nothing is settled.
- 07tinyx402 → facilitator/settleOnly for 2xx responses.
- 08facilitator → WhitechaintransferWithAuthorizationFacilitator pays the gas.
- 09tinyx402 → client200 + PAYMENT-RESPONSEResult plus receipt with the tx hash.
1. Payment required
Unpaid requests get 402 with a PAYMENT-REQUIRED header. Decoded, it looks like this:
{
"x402Version": 2,
"resource": { "url": "https://…/api/v1/search?query=x402", "description": "Structured web search results" },
"accepts": [{
"scheme": "exact",
"network": "eip155:1874",
"asset": "0xcedfB56113143d55b2358b8CFbB0dEB5Ad2f55a7",
"amount": "10000",
"payTo": "0xYourPayToAddress",
"maxTimeoutSeconds": 300,
"extra": { "name": "Tether USD", "version": "1" }
}]
}extra is the token's EIP-712 domain. The client needs it to produce a signature the USDT contract will accept.
2. Sign, don't send
The client signs an EIP-3009 TransferWithAuthorization message: from you, to payTo, value amount, a validity window and a random nonce. Signing is free and off-chain. Your wallet never broadcasts a transaction, so you need no native gas token.
3. Verify, work, settle
tinyx402 asks the facilitator to verify the signature and your balance, then does the work. It only asks the facilitator to settle when the response is successful. The facilitator submits transferWithAuthorization on Whitechain and pays the gas.
4. Receipt
The final response carries PAYMENT-RESPONSE:
{ "success": true, "transaction": "0x8c1f…", "network": "eip155:1874", "payer": "0xYou…" }Where it lives in the code
tinyx402 uses the official x402 SDKs (@x402/next, @x402/core, @x402/evm, @x402/fetch) and doesn't call the token contract itself.
| Step | File | What |
|---|---|---|
Price and PAYMENT-REQUIRED | lib/x402.ts | paid() builds the exact route config (asset, amount, payTo, EIP-712 extra). |
| Middleware | lib/x402.ts | paidRoute() wraps every handler in withX402 from @x402/next. |
| Facilitator | lib/x402.ts | HTTPFacilitatorClient({ url: FACILITATOR_URL }), registered on x402ResourceServer with ExactEvmScheme. |
| Verify, then settle | withX402 / paidStream() | /verify before the handler runs, /settle only for responses under 400. |
| Browser client | lib/pay-client.ts | createPaidFetch(): @x402/fetch plus the wallet's signTypedData. |
| Script client | scripts/pay.ts | @x402/fetch with a private-key signer. |
The middleware is per route (withX402) rather than a proxy.ts paymentProxy. paymentProxy would also settle failed responses, and it can't price from the request body or stream.
Probe it yourself:
curl -i "$SITE/api/v1/search?query=x402" # 402 + PAYMENT-REQUIRED
curl https://whitechain.007575.xyz/supported # facilitator: exact on eip155:1874Streaming: Agent (SSE)
/api/v1/agent/run-sse verifies the payment before the stream opens and settles only when the run reports COMPLETED. Headers are already sent by then, so the receipt arrives as the last event:
event: payment
data: {"success":true,"transaction":"0x…","payer":"0x…","network":"eip155:1874"}
event: done
data: {"ok":true}If the run fails, you receive event: error with "charged": false, and the authorization is never submitted.
Long-running jobs: Research and Datasets
Research (5–45 minutes) and dataset fills (5–20 minutes) outlast any HTTP request. Their paid call starts the job and settles as soon as TinyFish accepts it. The response carries the job ID plus events_url and poll_url, which you can follow for free. A job that later fails is not refunded automatically.