CA
Hood x402

Documentation

Everything you need to sell API calls over HTTP 402, from the three step wizard down to the wire protocol underneath it.

Overview#

Hood x402 takes an endpoint you already have and puts a price on it. You give us a URL or some content, set a price in USDG, and get back a live URL that enforces the 402 Payment Required handshake. If you would rather run the gate on your own servers, the same screen hands you working middleware for nine platforms.

Buyers do not register. A client calls your endpoint, reads the price out of the 402 response, signs a USDG authorization with its wallet, calls again and gets the content plus a receipt. That path is identical for a developer poking at it with curl and for an agent running x402-fetch on its own.

Quickstart#

  1. Open Create endpoint and choose what you are selling: proxy an API you already run, write a new JSON response, or paywall some static content.
  2. Set the price per request, the wallet your USDG should land in, and a facilitator. Leave it on Mock while you are still testing.
  3. Press Generate. You get a live /p/<id> URL, an endpoint secret that is shown once and never again, integration snippets and a playground.

You can try it right away. The playground signs a real EIP-3009 authorization with a throwaway key that lives only in your browser and walks the whole flow without touching any funds.

How a payment works#

One sale is two HTTP requests. Nothing else.

1. GET /p/ab12cd34
   -> 402 Payment Required
   {
     "x402Version": 1,
     "error": "X-PAYMENT header is required",
     "accepts": [{
       "scheme": "exact",
       "network": "eip155:4663",
       "maxAmountRequired": "1000",        // 0.001 USDG at 6 decimals
       "resource": "https://.../p/ab12cd34",
       "payTo": "0xYourPayoutWallet",
       "asset": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
       "maxTimeoutSeconds": 300,
       "extra": { "name": "USDG", "version": "1" }
     }]
   }

2. The client signs an EIP-3009 transferWithAuthorization for that
   amount and asks again:

   GET /p/ab12cd34
   X-PAYMENT: base64({ x402Version, scheme, network, payload: {
     signature, authorization: { from, to, value,
     validAfter, validBefore, nonce } } })

   -> 200 OK  (your content)
   X-PAYMENT-RESPONSE: base64({ success, txHash, networkId })

On our side every paid request passes eight checks: header parsing, scheme and network and recipient and amount, the validity window with a 60 second settlement buffer, replay protection through an in flight nonce claim plus settled nonce history, facilitator signature verification, fulfillment, settlement, receipt.

Pricing schemes#

exact means every request costs the listed price and the authorization has to match it down to the atomic unit.

upto means the client authorizes the full cap before it gets anything. Today settlement happens at that cap, and metered settlement below it is on the roadmap. The full cap rule is deliberate: settlement can never take more than the signed amount, so if we accepted less the buyer would be setting the price instead of you.

Network and USDG#

  • Network: Robinhood Chain, chain id 4663, written as eip155:4663 in CAIP-2 form
  • Asset: USDG at 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 with 6 decimals, so $0.001 is 1000 atomic units
  • Transfer: EIP-3009 transferWithAuthorization. The buyer signs typed data and the facilitator submits it, which is why buyers never need a gas token.
  • EIP-712 domain: { name: "Global Dollar", version: "1" }. Note the name is not "USDG". It is read from the contract, and npm run check:mainnet rebuilds DOMAIN_SEPARATOR from it and fails if the two ever drift, because a wrong domain means every signature is rejected on chain.
  • USDG routes calls to facets, and the EIP-3009 facet also exposes transferWithAuthorizationBatch, which is the ground floor for batching settlements later.

Facilitators#

A facilitator checks payment signatures and settles transfers on chain. Every endpoint points at one.

  • On-chain, built in, real money. Checks the signature against USDG on mainnet, refuses a nonce the token has already seen, refuses a payer who cannot cover the amount, simulates the call, then submits transferWithAuthorization from the deployment's relayer wallet. The relayer only pays gas. USDG moves from the payer straight to your payout address. Needs RELAYER_PRIVATE_KEY and a little ETH on Robinhood Chain.
  • Mock, built in. Checks real EIP-712 signatures, offline for normal wallets and over RPC for smart accounts (ERC-1271 and ERC-6492), then pretends to settle. Costs nothing to test against.
  • Primer, Solvador, Naven, AceDataCloud. Hosted facilitators for Robinhood Chain. Their base URLs come from the FACILITATOR_URL_* environment variables.
  • Self hosted. Any base URL that answers POST /verify and POST /settle.

Generated snippets point at /facilitator/<key>/verify and /settle, a relay this app serves so middleware deployed anywhere can resolve a registry key without knowing our environment variables.

Integration code#

The Code tab on an endpoint generates nine targets, each already filled in with your price, payout wallet and facilitator.

  • Express, Next.js, Hono using the official x402-express, x402-next and x402-hono middleware
  • Python on FastAPI with the x402 package
  • Cloudflare Worker and Vercel Edge, written against the raw protocol with no dependencies. They strip sensitive headers and forward request bodies the same way our runtime does.
  • Client code that consumes any paid endpoint via x402-fetch
  • MCP server that exposes the endpoint as a paid tool for agents
  • cURL for walking the flow by hand

One detail worth knowing: your JSON body, static content and origin URL only appear in snippets when you hold the endpoint secret, which is the case on the screen right after you create it. Elsewhere the snippets fall back to placeholders.

HTTP API#

POST/api/endpoints

Creates an endpoint. The JSON body takes name, description (optional), mode as one of proxy, json or static, then whichever of originUrl, jsonBody, staticContent and mimeType that mode needs, plus price above zero, scheme, payTo and facilitator.

Answers 201 with the full record, including the one time secret.

GET/api/endpoints

Lists endpoints. Metadata only. Paid content and origin URLs never appear in a request that has no secret.

GET/api/endpoints/:id

One endpoint. Send x-endpoint-secret if you also want the content fields back.

PATCH/api/endpoints/:id

Updates name, description, price, payTo, facilitator or active. Needs x-endpoint-secret.

DELETE/api/endpoints/:id

Removes the endpoint and its payment history. Needs x-endpoint-secret.

ANY/p/:id

The paid endpoint itself. With no X-PAYMENT header it answers 402 and the payment terms. With a valid payment it verifies, fulfills, settles and returns X-PAYMENT-RESPONSE. A paused endpoint answers 410. Nobody is charged when the origin returns 5xx or fails with 401, 403, 407 or 429.

POST/facilitator/:key/verify | settle

The relay generated snippets talk to. Takes { paymentPayload, paymentRequirements } and accepts registry keys only.

GET/.well-known/x402

Discovery feed listing active endpoints with their payment terms. This is how agents and indexers find your API.

GET/api/stats

Dashboard numbers: totals, revenue per endpoint and recent payments.

Agents and discovery#

x402 exists so software can buy from software. Any agent on x402-fetch, or anything that implements the two request handshake, can pay for your endpoint with nobody watching.

import { wrapFetchWithPayment } from "x402-fetch";
import { privateKeyToAccount } from "viem/accounts";

const fetchWithPay = wrapFetchWithPayment(
  fetch,
  privateKeyToAccount(process.env.PRIVATE_KEY),
);
const res = await fetchWithPay("https://hood402.com/p/ab12cd34");

Your active endpoints show up automatically at GET /.well-known/x402 with their id, price, scheme and full payment terms, and none of your content. Agent marketplaces such as the x402 Bazaar index feeds in exactly this shape.

MCP export#

The MCP tab writes you a ready to run Model Context Protocol server that wraps your paid endpoint as a tool. Point Claude or any other MCP client at it, fund a wallet with USDG, and the model can call your API and pay per request on its own.

Self hosting#

git clone <this repo> && cd hood-x402
npm install
npm run dev              # http://localhost:3000
npm run test:e2e         # wire protocol, 44 assertions
npm run test:ssrf        # redirect hop guard
npm run check:mainnet    # config vs the real chain
npm run check:settlement # signs against real USDG, spends nothing

Environment values live in .env.local:

  • UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN switch on the shared store. Required on serverless hosts such as Vercel: without them each instance keeps its own copy, so an endpoint created by one instance answers 404 on another and replay protection covers only one instance. Vercel KV variable names are accepted too.
  • ALLOW_MOCK_FACILITATOR to keep the test facilitator usable in production. It approves any well formed signature, so an open instance would work as a free request relay. Off by default in production.
  • HOOD_X402_CREATE_TOKEN to require a token on endpoint creation, which is what keeps a public URL from being used for spam or as a relay.
  • NEXT_PUBLIC_ROBINHOOD_RPC_URL for the chain RPC, used when checking smart account signatures
  • ALLOW_LOCAL_ORIGINS to permit localhost proxy origins. Development only.
  • FACILITATOR_URL_PRIMER, _SOLVADOR, _NAVEN, _ACEDATACLOUD for hosted facilitator base URLs. An endpoint that names an unconfigured facilitator refuses payments instead of quietly falling back.
  • HOOD_X402_DB_PATH for the JSON store location when you run a single long lived process. For any other backend, implement StorageAdapter in src/lib/store and nothing else has to change.

Before going live: configure the shared store, set ALLOW_LOCAL_ORIGINS=false, point at a real facilitator, decide whether creation needs a token, and confirm the on chain EIP-712 domain for USDG.

Security#

  • Replay protection. A nonce is claimed synchronously while it is in flight, so concurrent duplicates lose, and settled nonces are remembered afterwards.
  • Settlement buffer. An authorization has to stay valid for at least 60 more seconds when we accept it, so your origin never does work that settlement cannot collect for.
  • Fair charging. Nothing settles on 5xx or on origin side failures like 401 and 429. Buyer side mistakes such as 404 still settle, otherwise the proxy could be used for free.
  • SSRF guard. Proxy origins have to be public http or https hosts, and every redirect hop is checked again before we follow it. IPv4 mapped IPv6 and decimal or hex IP forms are covered.
  • Content privacy. Paid content and origin URLs come back only with the endpoint secret, so the paywall cannot be walked around through the management API.
  • Escaped code generation. Every value that enters a snippet is JSON escaped, and origin paths containing route pattern characters fall back to a safe placeholder.
  • No caching of paid responses. Cache headers from the origin are dropped and replaced with no-store, so a shared cache can never serve paid content to somebody who did not pay.

Troubleshooting#

402 saying the nonce was already used. Each authorization is good for one settlement, so sign a fresh one per request. The playground already does this for you.

402 saying the authorization expires too soon. Push validBefore at least 60 seconds into the future. Most clients use now plus 300.

Your browser wallet refuses to sign. Accept the network switch prompt. Injected wallets only sign typed data when their active chain matches chain id 4663.

Origin rejected. The URL resolves to a private address. In development you can set ALLOW_LOCAL_ORIGINS=true.

You lost the endpoint secret. It is shown once at creation and cannot be recovered, so create a new endpoint.