Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

x402 facilitator API

Payment verification and settlement endpoints

A facilitator is a service that verifies x402 payment signatures and settles them on-chain. This page documents the facilitator HTTP endpoints, TypeScript types, headers, status codes, and endorsed facilitators for Radius.

For integration patterns and settlement strategies, see x402 integration.

Endpoints

GET /supported

Returns the facilitator's supported networks, schemes, and signer addresses.

Parameters: None

Response body:
FieldTypeDescription
kindsarrayList of supported payment kinds
kinds[].x402VersionnumberProtocol version (1 or 2)
kinds[].schemestringPayment scheme (for example, exact)
kinds[].networkstringCAIP-2 network identifier
kinds[].extraobjectScheme-specific metadata
kinds[].extra.assetTransferMethodstringTransfer method: permit2 (Radius) or erc2612 (Stablecoin.xyz)
kinds[].extra.namestringERC-2612 permit domain name
kinds[].extra.versionstringERC-2612 permit domain version
extensionsarraySupported protocol extensions (for example, eip2612GasSponsoring for gasless Permit2 approval)
signersobjectMap of CAIP-2 patterns to signer addresses
Example response (Radius mainnet facilitator):
{
  "kinds": [
    {
      "x402Version": 2,
      "scheme": "exact",
      "network": "eip155:723487",
      "extra": {
        "assetTransferMethod": "permit2",
        "name": "Stable Coin",
        "version": "1"
      }
    }
  ],
  "extensions": ["eip2612GasSponsoring"],
  "signers": {}
}

The Radius testnet facilitator (https://facilitator.testnet.radiustech.xyz/supported) returns an equivalent response with "network": "eip155:72344".

assetTransferMethod is "permit2" for the Radius facilitator or "erc2612" for Stablecoin.xyz. The "eip2612GasSponsoring" extension means the facilitator handles the one-time Permit2 approval gaslessly if the payer hasn't already approved the Permit2 contract.

Example request:
curl https://facilitator.radiustech.xyz/supported

POST /verify

Verifies a payment signature without settling on-chain. Use this endpoint to validate payment data before committing to settlement.

Request body:

The server passes the decoded PAYMENT-SIGNATURE value directly as paymentPayload and adds a paymentRequirements object built from its own config:

FieldTypeDescription
x402VersionnumberProtocol version (2)
paymentPayloadobjectThe decoded client payload from the PAYMENT-SIGNATURE header
paymentPayload.x402VersionnumberProtocol version (2)
paymentPayload.resourceobject(Optional) Resource info echoed from the 402 response
paymentPayload.acceptedobjectThe payment method the client selected
paymentPayload.accepted.schemestringPayment scheme (for example, exact)
paymentPayload.accepted.networkstringCAIP-2 network identifier
paymentPayload.accepted.amountstringPayment amount in raw token units
paymentPayload.accepted.assetstringERC-20 token contract address
paymentPayload.accepted.payTostringRecipient address
paymentPayload.accepted.maxTimeoutSecondsnumberMaximum time the payment is valid
paymentPayload.accepted.extraobjectScheme-specific metadata
paymentPayload.payloadobjectSigned authorization data
paymentPayload.payload.signaturestringSingle 65-byte hex-encoded signature
paymentPayload.payload.authorizationobjectEIP-2612 permit fields
paymentPayload.payload.authorization.fromstringPayer address
paymentPayload.payload.authorization.tostringSpender address (for EIP-2612 flows: the settlement wallet; for Permit2 flows: the x402ExactPermit2Proxy)
paymentPayload.payload.authorization.valuestringPayment amount in raw token units
paymentPayload.payload.authorization.validAfterstringEarliest valid timestamp
paymentPayload.payload.authorization.validBeforestringLatest valid timestamp
paymentPayload.payload.authorization.noncestringUnique nonce for replay protection
paymentPayload.extensionsobject(Optional) Protocol extensions data
paymentRequirementsobjectExpected payment parameters from the server config
paymentRequirements.schemestringPayment scheme (for example, exact)
paymentRequirements.networkstringCAIP-2 network identifier
paymentRequirements.amountstringRequired amount in raw token units
paymentRequirements.assetstringERC-20 token contract address
paymentRequirements.payTostringExpected recipient address
paymentRequirements.maxTimeoutSecondsnumberMaximum time the payment is valid
paymentRequirements.extraobjectToken permit domain metadata

When using a Permit2-based facilitator, the payment payload uses permit2Authorization instead of authorization, with different fields (permitted, spender, witness). The /verify and /settle request format is the same — the facilitator interprets the payload based on the assetTransferMethod. See the x402 exact EVM scheme spec for the Permit2 payload structure.

Response body:
FieldTypeDescription
isValidbooleantrue if the payment signature and balance check pass
payerstringAddress of the payer
invalidReasonstring(Optional) Machine-readable reason for failure if isValid is false
invalidMessagestring(Optional) Human-readable failure message
extensionsobject(Optional) Protocol extensions data
Example request (EIP-2612 payload format):

The example below uses the EIP-2612 authorization format. For the Permit2 permit2Authorization format used with the Radius facilitator, see the x402 exact EVM scheme spec.

curl -X POST https://facilitator.radiustech.xyz/verify \
  -H "Content-Type: application/json" \
  -d '{
    "x402Version": 2,
    "paymentPayload": {
      "x402Version": 2,
      "accepted": {
        "scheme": "exact",
        "network": "eip155:723487",
        "amount": "100",
        "asset": "0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb",
        "payTo": "{{MERCHANT_ADDRESS}}",
        "maxTimeoutSeconds": 300,
        "extra": {
          "assetTransferMethod": "erc2612",
          "name": "Stable Coin",
          "version": "1"
        }
      },
      "payload": {
        "signature": "{{SIGNATURE}}",
        "authorization": {
          "from": "{{PAYER_ADDRESS}}",
          "to": "{{MERCHANT_ADDRESS}}",
          "value": "100",
          "validAfter": "0",
          "validBefore": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
          "nonce": "{{NONCE}}"
        }
      }
    },
    "paymentRequirements": {
      "scheme": "exact",
      "network": "eip155:723487",
      "amount": "100",
      "asset": "0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb",
      "payTo": "{{MERCHANT_ADDRESS}}",
      "maxTimeoutSeconds": 300,
      "extra": { "name": "Stable Coin", "version": "1" }
    }
  }'
Example response (success):
{ "isValid": true, "payer": "{{PAYER_ADDRESS}}" }
Example response (failure):
{ "isValid": false, "invalidReason": "insufficient_funds", "invalidMessage": "Payer balance is below the required amount", "payer": "{{PAYER_ADDRESS}}" }

POST /settle

Settles a verified payment on-chain. The request body matches the /verify endpoint. On success, the facilitator submits the permit and transfer transactions to Radius and returns the transaction hash.

Request body: Same as POST /verify. For EIP-2612 flows, the facilitator splits the signature into v, r, s internally when calling the on-chain permit() function. For Permit2 flows, the facilitator forwards the signature to the x402ExactPermit2Proxy contract.

Response body:
FieldTypeDescription
successbooleantrue if settlement completed
transactionstringOn-chain transaction hash (empty string if failed)
networkstringCAIP-2 network identifier where settlement occurred
payerstringAddress of the payer
errorReasonstring(Optional) Machine-readable reason for failure if success is false
errorMessagestring(Optional) Human-readable failure message
extensionsobject(Optional) Protocol extensions data
Example request (EIP-2612 payload format):
curl -X POST https://facilitator.radiustech.xyz/settle \
  -H "Content-Type: application/json" \
  -d '{
    "x402Version": 2,
    "paymentPayload": {
      "x402Version": 2,
      "accepted": {
        "scheme": "exact",
        "network": "eip155:723487",
        "amount": "100",
        "asset": "0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb",
        "payTo": "{{MERCHANT_ADDRESS}}",
        "maxTimeoutSeconds": 300,
        "extra": {
          "assetTransferMethod": "erc2612",
          "name": "Stable Coin",
          "version": "1"
        }
      },
      "payload": {
        "signature": "{{SIGNATURE}}",
        "authorization": {
          "from": "{{PAYER_ADDRESS}}",
          "to": "{{MERCHANT_ADDRESS}}",
          "value": "100",
          "validAfter": "0",
          "validBefore": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
          "nonce": "{{NONCE}}"
        }
      }
    },
    "paymentRequirements": {
      "scheme": "exact",
      "network": "eip155:723487",
      "amount": "100",
      "asset": "0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb",
      "payTo": "{{MERCHANT_ADDRESS}}",
      "maxTimeoutSeconds": 300,
      "extra": { "name": "Stable Coin", "version": "1" }
    }
  }'
Example response (success):
{
  "success": true,
  "transaction": "0x...",
  "network": "eip155:723487",
  "payer": "{{PAYER_ADDRESS}}"
}
Example response (failure):
{
  "success": false,
  "errorReason": "insufficient_funds",
  "errorMessage": "Payer balance is below the required amount",
  "transaction": "",
  "network": "eip155:723487",
  "payer": "{{PAYER_ADDRESS}}"
}

GET /health

Returns the facilitator's health status.

Parameters: None

Example request:
curl https://facilitator.radiustech.xyz/health

TypeScript types

X402Config

Configuration for x402 payment gating on a server or gateway.

interface X402Config {
  /** ERC-20 token contract address */
  asset: string;
  /** CAIP-2 chain identifier, e.g. "eip155:723487" */
  network: string;
  /** Wallet address that receives payments */
  payTo: string;
  /** Facilitator service base URL */
  facilitatorUrl: string;
  /** Payment amount in raw token units (e.g. "100" = 0.0001 SBC with 6 decimals) */
  amount: string;
  /** Optional API key for the facilitator */
  facilitatorApiKey?: string;
  /** ERC-2612 permit domain name (default: "Stable Coin") */
  tokenName?: string;
  /** ERC-2612 permit domain version (default: "1") */
  tokenVersion?: string;
  /** HTTP header carrying the payment (default: "PAYMENT-SIGNATURE") */
  paymentHeader?: string;
}

PaymentRequired

The object encoded in the PAYMENT-REQUIRED header when a resource requires payment.

interface PaymentRequired {
  x402Version: number;
  error?: string;
  resource: {
    url: string;
    description?: string;
    mimeType?: string;
  };
  accepts: PaymentRequirementsItem[];
  extensions?: Record<string, unknown>;
}

PaymentRequirementsItem

A single accepted payment method within PaymentRequired.accepts.

interface PaymentRequirementsItem {
  scheme: string;
  network: string;
  amount: string;
  asset: string;
  payTo: string;
  maxTimeoutSeconds: number;
  extra: Record<string, unknown>;
}

PaymentOutcome

All possible outcomes of a payment flow, used by server-side middleware to determine HTTP responses.

type PaymentOutcome =
  | { status: 'no-payment'; requirements: PaymentRequired }
  | { status: 'invalid-header' }
  | { status: 'verify-failed'; detail: any }
  | { status: 'verify-unreachable'; detail: string }
  | { status: 'settle-failed'; detail: any }
  | { status: 'settle-unreachable'; detail: string }
  | {
      status: 'settled';
      transaction: string | undefined;
      payer: string;
      network: string;
      verifyMs: number;
      settleMs: number;
      totalMs: number;
    }
  | { status: 'settle-pending'; verifyMs: number; totalMs: number };

HTTP headers

HeaderDirectionDescription
PAYMENT-REQUIREDResponseBase64-encoded PaymentRequired object returned in a 402 response
PAYMENT-SIGNATURERequestBase64-encoded payment payload sent by the client
PAYMENT-RESPONSEResponseBase64-encoded SettleResponse object returned on a 200 after settlement
x-api-keyRequestAPI key for facilitator authentication (required for mainnet)

The client reads the PAYMENT-REQUIRED header from the 402 response, constructs a signed payment, Base64-encodes it, and attaches it as PAYMENT-SIGNATURE on the retry request. On success, the server returns a PAYMENT-RESPONSE header containing the settlement details.

Status code mapping

Each PaymentOutcome status maps to an HTTP status code returned by the server middleware:

PaymentOutcome statusHTTP statusMeaning
no-payment402No PAYMENT-SIGNATURE header present
invalid-header400Malformed PAYMENT-SIGNATURE header
verify-failed402Signature or balance check failed
verify-unreachable502Facilitator /verify endpoint unreachable
settle-failed402On-chain settlement rejected
settle-unreachable502Facilitator /settle endpoint unreachable
settle-pending200Async settlement fired, resource served
settled200Payment confirmed on-chain, resource served

Facilitators supporting Radius

FacilitatorURLRadius networksNotes
Radius (recommended)Mainnet: https://facilitator.radiustech.xyz Testnet: https://facilitator.testnet.radiustech.xyzMainnet + TestnetFirst-party, Permit2 + gas sponsoring, v2
Stablecoin.xyzhttps://x402.stablecoin.xyzMainnet + TestnetSBC via EIP-2612, v1 and v2
Middlebithttps://middlebit.comMainnetMiddleware layer, uses Stablecoin.xyz for settlement

Network reference

PropertyMainnetTestnet
Chain ID72348772344
CAIP-2eip155:723487eip155:72344
SBC token0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb
SBC decimals66
Permit domain nameStable CoinStable Coin
Permit domain version11

Raw token units use 6 decimals. A value of "100" equals 0.0001 SBC. A value of "1000000" equals 1 SBC.

Troubleshooting

ErrorCauseAction
"No available wallets in pool"Facilitator settlement wallets temporarily exhaustedRetry after 1-2s; contact operator if persistent
HTTP 502Facilitator unreachableRetry or switch to alternate facilitator
"Permit expired"Payment deadline passedClient re-signs with fresh deadline

See x402 integration — Troubleshooting for details.

Note for facilitator operators

The Radius facilitator does not currently expose permit2Address or x402Permit2ProxyAddress in the /supported response. Including these as optional fields in extra for permit2 entries would make integration self-documenting, removing the need for integrators to look up canonical addresses:

"extra": {
  "assetTransferMethod": "permit2",
  "permit2Address": "0x000000000022D473030F116dDEE9F6B43aC78BA3",
  "x402Permit2ProxyAddress": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001",
  "name": "Stable Coin",
  "version": "1"
}

Related pages