Facilitator API.
The five endpoints of x402.aeron.sh: verify, settle, supported, status, healthz — with every field, every refusal reason, and what to do about each.
The facilitator is one Cloudflare Worker with five endpoints and no state. It settles a single asset — USDG on Robinhood Chain — under the x402 exact scheme, and it is deliberately strict: every check below is a refusal, not a warning.
- Base URL
- https://x402.aeron.sh
- Auth
- none until 30 September 2026, credits after
- CORS
- not sent — call it server-side
- Content type
- application/json on POST
| Endpoint | Purpose |
|---|---|
GET /supported | The scheme and network this facilitator settles. |
GET /status | Network, asset, relayer address, and relayer gas balance. |
GET /healthz | Liveness, and whether settlement is enabled. |
POST /verify | Is this authorization real, unspent, and funded? No state changes. |
POST /settle | Verify, then submit the transfer on chain and return the hash. |
GET /supported
curl -s https://x402.aeron.sh/supported
{"kinds":[{"x402Version":1,"scheme":"exact","network":"eip155:4663"}]}GET /status
relayerEth is the gas budget behind every settlement. When it reaches zero, /settle starts failing and buyers see payment failures — alert on it if you depend on this facilitator.
curl -s https://x402.aeron.sh/status
{
"name": "Aeron Facilitator",
"network": "eip155:4663",
"asset": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
"scheme": "exact",
"settlement": "enabled", // or "verify-only"
"relayer": "0x2b94e4188da990EcF84d13A5AbE69BC9523015A7",
"relayerEth": "0.100773054387092",
"endpoints": ["/supported","/verify","/settle","/status","/healthz"]
}POST /verify
Body is the same for both POST endpoints:
{
"x402Version": 1,
"paymentPayload": {
"x402Version": 1,
"scheme": "exact",
"network": "eip155:4663",
"payload": {
"signature": "0x…",
"authorization": {
"from": "0x7a1c…9e42",
"to": "0x3fd0…b118",
"value": "4000",
"validAfter": "1756703940",
"validBefore": "1756704600",
"nonce": "0x9f3c…d201"
}
}
},
"paymentRequirements": {
"scheme": "exact",
"network": "eip155:4663",
"maxAmountRequired": "4000",
"payTo": "0x3fd0…b118",
"asset": "0x5fc5360d0400a0fd4f2af552add042d716f1d168"
}
}Fields
| Field | Rule |
|---|---|
payload.signature | 0x + 130 hex characters. Must recover to authorization.from under the token’s EIP-712 domain. |
authorization.from | The payer. Must hold at least value in USDG at verification time. |
authorization.to | Must equal paymentRequirements.payTo, case-insensitive. |
authorization.value | Decimal string, atomic units. Must equal maxAmountRequired exactly. |
authorization.validAfter | Unix seconds, strictly in the past. Sign it as now - 60; equal to now is refused. |
authorization.validBefore | Unix seconds, strictly in the future. Leave room for settlement — the reference wallet uses now + maxTimeoutSeconds + 540. |
authorization.nonce | 0x + 64 hex characters, random. Checked against the token’s authorizationState; single use per payer. |
network | eip155:4663, in the payload and in the requirements. |
asset | The USDG address. Any other token is refused. |
paymentRequirements tolerates extra keys, so you can send the whole offer you published in your 402 — resource, description, mimeType, maxTimeoutSeconds, extra are carried and ignored.
Response
{ "isValid": true, "payer": "0x7a1c…9e42" }
{ "isValid": false, "payer": null, "invalidReason": "insufficient USDG balance" }Refusal reasons
Every one is returned verbatim in invalidReason. They are worth surfacing to your caller: an agent operator reading a log needs to know whether to retry, top up, or fix the seller.
| Reason | Means |
|---|---|
wrong network; expected eip155:4663 | The payload or the requirements named another network. |
unsupported asset; this facilitator settles USDG only | asset is not the USDG contract. |
authorization.to does not match payTo | The buyer signed a transfer to a different recipient than the one you asked for. |
authorization.value does not match maxAmountRequired | Amount mismatch. Exact means exact. |
authorization not valid yet | validAfter is now or later. Backdate it by a minute. |
authorization expired | validBefore has passed. The buyer must sign again. |
invalid signature | The signature does not recover to from — usually a wrong EIP-712 domain or a mutated field. |
authorization nonce already used | That authorization has been spent. Do not deliver twice on it. |
insufficient USDG balance | The payer cannot cover it. Top up and sign again. |
malformed request body | Returned with HTTP 400 when the body fails schema validation. |
POST /settle
Settlement re-verifies first, then submits transferWithAuthorization from the relayer and waits for the receipt. Allow up to 60 seconds; a short client timeout can abort a payment that in fact settled.
{
"success": true,
"transaction": "0xca33d2264843720330a8ace2d93a98fd3f7ef52d4711e9e48f3f3bab48d84dce",
"network": "eip155:4663",
"payer": "0x7a1c…9e42"
}// verification failed: nothing was submitted, the authorization is unspent
{ "success": false, "transaction": null, "network": "eip155:4663",
"errorReason": "authorization nonce already used" }errorReason | Means |
|---|---|
| Any refusal reason above | Verification failed inside settle. Nothing was submitted. |
settlement disabled: no relayer key configured | This facilitator is running verify-only. Check /status. |
transaction reverted | Submitted and mined, but the token rejected it. transaction holds the hash — read the revert on the explorer. |
| anything else | The RPC or relayer error, verbatim. Out of gas at the relayer surfaces here. |
Status codes
| Code | When |
|---|---|
200 | The request was well formed. A refusal is a 200 with isValid: false or success: false — read the body, not the status. |
400 | The body failed schema validation. |
404 | Unknown path, or the wrong method for a known one. |
503 | {"error":"facilitator unavailable"} — chain setup failed on a cold isolate, usually an RPC problem. Retry. |
The EIP-712 domain
Only needed if you sign authorizations yourself. It is a chain constant, pinned rather than discovered because rediscovering it on every cold isolate is enough to trip a public RPC’s rate limit.
{
"name": "Global Dollar",
"version": "1",
"chainId": 4663,
"verifyingContract": "0x5fc5360d0400a0fd4f2af552add042d716f1d168"
}Verify it yourself against the contract’s own DOMAIN_SEPARATOR rather than trusting this page: npm run check:token in the facilitator repo does exactly that.
Run your own
The facilitator is MIT licensed and deploys as a single Worker: aeronlabs/aeron-facilitator. You need a relayer key with a little ETH and, in practice, a keyed RPC endpoint — the public Robinhood RPC meters by IP, and Cloudflare’s egress is shared, so a Worker on the public endpoint gets rate-limited into 503s.
Running your own is also the answer if you want a gas policy other than the one here. Aeronscan indexes settlements by the address that submitted them, so your facilitator appears under its own name, with its real numbers, without registering anywhere.
Updated 1 September 2026. Everything on this page is read from the running services; report a drift at github.com/aeronlabs.