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 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
EndpointPurpose
GET /supportedThe scheme and network this facilitator settles.
GET /statusNetwork, asset, relayer address, and relayer gas balance.
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":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.

bash200
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:

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 in USDG 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, in the payload and in the requirements.
assetThe 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

json200 — both cases
{ "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.

ReasonMeans
wrong network; expected eip155:4663The payload or the requirements named another network.
unsupported asset; this facilitator settles USDG onlyasset is not the USDG contract.
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.
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 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.

jsondomain
{
  "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.