Charge an agent for an HTTP call.
Turn any HTTP endpoint into a paid one with x402: answer 402 with your price, verify the payment, do the work, then settle through the Aeron facilitator.
Any HTTP endpoint becomes machine-payable by answering 402 with a price and then checking the payment that comes back. You do not hold a wallet key, you do not run a node, and you never touch the buyer’s funds — the facilitator verifies the signature and relays the transfer, and the money lands at your address in one transaction.
You need three things: an address that can receive USDG, a price, and a server that can make two outbound HTTPS calls.
No key, no registration, no allowlist through 30 September 2026, up to a shared allowance of 1,000,000 settlements. Point your server at https://x402.aeron.sh and it works. From 1 October 2026, /settle draws gas from credits — see the policy before you build something that must not break that day.
Configure
Four values. The first three are fixed by the network; only the last is yours.
FACILITATOR_URL=https://x402.aeron.sh NETWORK=eip155:4663 USDG_ADDRESS=0x5fc5360d0400a0fd4f2af552add042d716f1d168 PAY_TO=0xYourReceivingAddress # yours; USDG lands here
Confirm the facilitator agrees before you write anything:
curl -s https://x402.aeron.sh/supported
{"kinds":[{"x402Version":1,"scheme":"exact","network":"eip155:4663"}]}Answer 402
A request with no X-PAYMENT header gets your price sheet. accepts is an array because the format allows several offers; one is normal.
{
"x402Version": 1,
"error": "X-PAYMENT header is required",
"accepts": [
{
"scheme": "exact",
"network": "eip155:4663",
"maxAmountRequired": "2000", // 0.002 USDG, atomic units
"resource": "https://api.example.com/v1/ocr",
"description": "One OCR page",
"mimeType": "application/json",
"payTo": "0xYourReceivingAddress",
"maxTimeoutSeconds": 60,
"asset": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
"extra": { "name": "USDG", "decimals": 6 }
}
]
}maxAmountRequired is the exact amount, despite the name: the facilitator refuses an authorization whose value is anything other than this number. Price in atomic units — USDG has six decimals, so $0.002 is "2000".
Read the payment
The client retries the same request with X-PAYMENT: base64 of a JSON object. Decode it, and pass it through untouched — you never need to inspect the signature yourself.
{
"x402Version": 1,
"scheme": "exact",
"network": "eip155:4663",
"payload": {
"signature": "0x…", // 65 bytes, 132 chars with 0x
"authorization": {
"from": "0xPayer…",
"to": "0xYourReceivingAddress", // must equal your payTo
"value": "2000", // must equal maxAmountRequired
"validAfter": "1756704000",
"validBefore": "1756704600",
"nonce": "0x…" // 32 bytes, single use
}
}
}Verify, work, settle — in that order
POST /verify proves the authorization is real, unspent, and funded, and changes nothing. POST /settle submits it. Both take the same body: your requirements plus the payload you decoded.
curl -s -X POST https://x402.aeron.sh/verify \
-H 'content-type: application/json' \
-d '{"x402Version":1,"paymentPayload":{…},"paymentRequirements":{…}}'
{"isValid":true,"payer":"0x7a1c…9e42"}Do the work between the two calls, not before verification and not after settlement:
- Settling first charges for your own failures. An upstream 429 or an empty result is not an exception — it arrives as an ordinary response and sails past any
try/catch, and the buyer has already paid for it. - Verifying first costs you the mirror risk: the payer could spend those funds elsewhere in the gap, leaving you with work you cannot charge for. That is bounded by one call, and it lands on you rather than on the buyer.
If /settle fails after the work is done, the honest answer is to charge nothing and return nothing: it is the only outcome that leaves neither side owed.
A complete seller
Forty lines, no dependencies, runs on Cloudflare Workers. Node, Deno, and Bun differ only in atob/btoa versus Buffer.
const FACILITATOR = 'https://x402.aeron.sh'
const NETWORK = 'eip155:4663'
const ASSET = '0x5fc5360d0400a0fd4f2af552add042d716f1d168'
const PAY_TO = '0xYourReceivingAddress'
const PRICE = '2000' // 0.002 USDG
const requirements = (resource) => ({
scheme: 'exact', network: NETWORK, maxAmountRequired: PRICE,
resource, description: 'One call', mimeType: 'application/json',
payTo: PAY_TO, maxTimeoutSeconds: 60, asset: ASSET,
extra: { name: 'USDG', decimals: 6 },
})
const json = (body, status = 200, headers = {}) =>
new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', ...headers } })
// /settle waits for the receipt, so give it more room than /verify.
async function call(path, paymentPayload, paymentRequirements, ms) {
const res = await fetch(FACILITATOR + path, {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ x402Version: 1, paymentPayload, paymentRequirements }),
signal: AbortSignal.timeout(ms),
})
if (!res.ok) throw new Error(path + ' HTTP ' + res.status)
return res.json()
}
export default {
async fetch(request) {
const url = new URL(request.url)
const reqs = requirements(url.origin + url.pathname)
const offer = (error) => json({ x402Version: 1, error, accepts: [reqs] }, 402)
const header = request.headers.get('x-payment')
if (!header) return offer('X-PAYMENT header is required')
let payment
try { payment = JSON.parse(atob(header)) } catch { return offer('malformed X-PAYMENT header') }
const verified = await call('/verify', payment, reqs, 30_000)
if (!verified.isValid) return offer(verified.invalidReason ?? 'invalid payment')
const result = await doTheWork(request, verified.payer) // your endpoint
const settled = await call('/settle', payment, reqs, 90_000)
if (!settled.success) return offer(settled.errorReason ?? 'settlement failed')
return json(result, 200, {
'x-payment-response': btoa(JSON.stringify({
success: true, transaction: settled.transaction, network: NETWORK, payer: verified.payer,
})),
})
},
}Test it end to end
Point the reference buyer at your own endpoint. It reads your 402, signs, and retries — and prints the transaction hash it got back.
npx -y aeron-wallet address # fund this with USDG, then:
npx -y aeron-wallet pay https://api.example.com/v1/ocr '{"url":"…"}'Your settlement appears on Aeronscan within a minute, and on Blockscout immediately. See the wallet docs for budget caps and scoped sessions.
What trips people up
| Rule | Why it bites |
|---|---|
| Exact amount | value must equal maxAmountRequired as an integer string. Not less, not more — a “maximum” reading of the field name is the single most common integration bug. |
| Network in two places | paymentPayload.network and paymentRequirements.network must both be eip155:4663. A missing one is a refusal, not a default. |
| Server-side only | The facilitator sends no CORS headers. Call it from your backend; a browser cannot. |
| Settle is slow | It waits for the transaction receipt — up to 60 seconds — so give /settle a timeout of its own, around 90 seconds. Sharing /verify’s 30 seconds aborts payments that in fact settled. |
| A timeout is not a refusal | An aborted /settle says nothing about the transfer: the relayer may already have broadcast it. Before you tell a buyer they were not charged, call /verify again — authorization nonce already used means that authorization was spent, and the settle you just sent is what spent it. |
| Nonces are single-use | Retrying a request with an already-settled X-PAYMENT returns authorization nonce already used. Treat that as “already paid” only if your own records agree; never deliver twice on one nonce. |
| USDG only | One asset, one chain. Another token, or the same token on another chain, is refused. |
Every refusal names its own reason. The full list, and what to do about each.
Updated 1 September 2026. Everything on this page is read from the running services; report a drift at github.com/aeronlabs.