AERONDocs GitHub
Reference

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 the x402 exact scheme on two rails — USDG on Robinhood Chain and USDC on Arc — and it is deliberately strict: every check below is a refusal, not a warning.

A payment says which rail it belongs to. /verify and /settle read the network in paymentRequirements and answer on that chain; nothing is inferred from the hostname, and a network this facilitator does not settle is refused by name. If you want one chain and one answer, https://x402-arc.aeron.sh is the same service with Arc alone.

Base URL
https://x402.aeron.sh
Rails
eip155:4663 — USDG · eip155:5042 — USDC on Arc
Auth
none until 30 September 2026, credits after
CORS
not sent — call it server-side
Content type
application/json on POST
EndpointPurpose
GET /supportedThe scheme and every network this facilitator settles.
GET /statusEvery rail: network, asset, relayer address, and the gas it has left.
GET /healthzLiveness, and whether settlement is enabled.
POST /verifyIs this authorization real, unspent, and funded? No state changes.
POST /settleVerify, then submit the transfer on chain and return the hash.

GET /supported

bash200
curl -s https://x402.aeron.sh/supported

{
  "kinds": [
    {"x402Version":2,"scheme":"exact","network":"eip155:4663"},
    {"x402Version":2,"scheme":"exact","network":"eip155:5042"}
  ],
  "extensions": [],
  "signers": {"eip155:*":["0x2b94e4188da990EcF84d13A5AbE69BC9523015A7"]}
}

One eip155:* entry means one relayer signs on every rail. A rail with its own signer is listed under its own network id instead, and a rail running verify-only is not listed at all — signers names who actually submits, which is what Aeronscan reads to attribute a settlement.

GET /status

The flat fields describe the first rail and are unchanged; networks carries all of them. Each rail has its own gas budget in whatever that chain charges gas in — ether on Robinhood Chain, USDC on Arc, since on Arc the dollar is the native asset. When one reaches zero, /settle fails on that rail and buyers see payment failures, so alert on every relayerNative, not just the top-level number.

bash200
curl -s https://x402.aeron.sh/status

{
  "name": "Aeron Facilitator",
  "network": "eip155:4663",             // the first rail, for monitors that predate the second
  "chain": "Robinhood Chain",
  "asset": "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
  "assetSymbol": "USDG",
  "scheme": "exact",
  "settlement": "enabled",              // or "verify-only"
  "relayer": "0x2b94e4188da990EcF84d13A5AbE69BC9523015A7",
  "relayerNative": "0.100773054387092",
  "gasSymbol": "ETH",
  "relayerEth": "0.100773054387092",    // the same number under its old name
  "networks": [
    {
      "network": "eip155:4663", "chain": "Robinhood Chain",
      "asset": "0x5fc5360d0400a0fd4f2af552add042d716f1d168", "assetSymbol": "USDG",
      "settlement": "enabled", "relayer": "0x2b94e4188da990EcF84d13A5AbE69BC9523015A7",
      "relayerNative": "0.100773054387092", "gasSymbol": "ETH",
      "explorer": "https://explorer.chain.robinhood.com"
    },
    {
      "network": "eip155:5042", "chain": "Arc",
      "asset": "0x3600000000000000000000000000000000000000", "assetSymbol": "USDC",
      "settlement": "enabled", "relayer": "0x2b94e4188da990EcF84d13A5AbE69BC9523015A7",
      "relayerNative": "4.812331", "gasSymbol": "USDC",
      "explorer": "https://explorer.arc.io"
    }
  ],
  "endpoints": ["/supported","/verify","/settle","/status","/healthz"]
}

POST /verify

Body is the same for both POST endpoints:

jsonrequest
{
  "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

FieldRule
payload.signature0x + 130 hex characters. Must recover to authorization.from under the token’s EIP-712 domain.
authorization.fromThe payer. Must hold at least value of that rail’s asset at verification time.
authorization.toMust equal paymentRequirements.payTo, case-insensitive.
authorization.valueDecimal string, atomic units. Must equal maxAmountRequired exactly.
authorization.validAfterUnix seconds, strictly in the past. Sign it as now - 60; equal to now is refused.
authorization.validBeforeUnix seconds, strictly in the future. Leave room for settlement — the reference wallet uses now + maxTimeoutSeconds + 540.
authorization.nonce0x + 64 hex characters, random. Checked against the token’s authorizationState; single use per payer.
networkeip155:4663 or eip155:5042, in the payload and in the requirements. The requirements pick the rail; a payload naming a different one is refused, not reconciled.
assetThe asset of the network you named: USDG on eip155:4663, USDC at 0x3600…0000 on eip155:5042. Any other token is refused, including the right token on the wrong rail.

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

json200 — both cases
{ "isValid": true,  "payer": "0x7a1c…9e42" }

{ "isValid": false, "invalidReason": "insufficient USDG balance" }   // no payer key on a refusal

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.

ReasonMeans
unsupported network eip155:8453; this facilitator settles eip155:4663, eip155:5042No rail here settles that chain. The list in the message is the live one — a rail whose RPC is down is left out of it.
wrong network; expected eip155:5042The requirements chose a rail and the payload named a different one.
unsupported asset; eip155:5042 settles USDC onlyasset is not that rail’s token. USDG on the Arc rail lands here.
authorization.to does not match payToThe buyer signed a transfer to a different recipient than the one you asked for.
authorization.value does not match maxAmountRequiredAmount mismatch. Exact means exact.
authorization not valid yetvalidAfter is now or later. Backdate it by a minute.
authorization expiredvalidBefore has passed. The buyer must sign again.
invalid signatureThe signature does not recover to from — usually a wrong EIP-712 domain or a mutated field.
authorization nonce already usedThat authorization has been spent. Do not deliver twice on it.
insufficient USDG balanceThe payer cannot cover it. Top up and sign again. The Arc rail says insufficient USDC balance.
malformed request bodyReturned 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.

json200 — settled
{
  "success": true,
  "transaction": "0xca33d2264843720330a8ace2d93a98fd3f7ef52d4711e9e48f3f3bab48d84dce",
  "network": "eip155:4663",
  "payer": "0x7a1c…9e42"
}
json200 — refused
// verification failed: nothing was submitted, the authorization is unspent
{ "success": false, "transaction": null, "network": "eip155:4663",
  "errorReason": "authorization nonce already used" }
errorReasonMeans
Any refusal reason aboveVerification failed inside settle. Nothing was submitted.
settlement disabled: no relayer key configuredThis facilitator is running verify-only. Check /status.
transaction revertedSubmitted and mined, but the token rejected it. transaction holds the hash — read the revert on the explorer.
anything elseThe RPC or relayer error, verbatim. Out of gas at the relayer surfaces here.

Status codes

CodeWhen
200The request was well formed. A refusal is a 200 with isValid: false or success: false — read the body, not the status.
400The body failed schema validation.
404Unknown 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 domains

Only needed if you sign authorizations yourself. One per rail — a domain is a property of the token on its chain, not of this service — and pinned rather than discovered, because rediscovering it on every cold isolate is enough to trip a public RPC’s rate limit. Sign against the domain of the chain you are paying on; the other one recovers to a different address and the payment reads as forged.

jsoneip155:4663
{
  "name": "Global Dollar",
  "version": "1",
  "chainId": 4663,
  "verifyingContract": "0x5fc5360d0400a0fd4f2af552add042d716f1d168"
}
jsoneip155:5042
{
  "name": "USDC",
  "version": "2",
  "chainId": 5042,
  "verifyingContract": "0x3600000000000000000000000000000000000000"
}

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. A chain is a preset there, and NETWORKS="robinhood,arc-mainnet" is how one Worker serves both. You need a relayer key with a little gas on each chain you serve — ether on Robinhood Chain, USDC on Arc — 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 20 September 2026. Everything on this page is read from the running services; report a drift at github.com/aeronlabs.