x402 facilitator API
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:| Field | Type | Description |
|---|---|---|
kinds | array | List of supported payment kinds |
kinds[].x402Version | number | Protocol version (1 or 2) |
kinds[].scheme | string | Payment scheme (for example, exact) |
kinds[].network | string | CAIP-2 network identifier |
kinds[].extra | object | Scheme-specific metadata |
kinds[].extra.assetTransferMethod | string | Transfer method: permit2 (Radius) or erc2612 (Stablecoin.xyz) |
kinds[].extra.name | string | ERC-2612 permit domain name |
kinds[].extra.version | string | ERC-2612 permit domain version |
extensions | array | Supported protocol extensions (for example, eip2612GasSponsoring for gasless Permit2 approval) |
signers | object | Map of CAIP-2 patterns to signer addresses |
{
"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.
curl https://facilitator.radiustech.xyz/supportedPOST /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:
| Field | Type | Description |
|---|---|---|
x402Version | number | Protocol version (2) |
paymentPayload | object | The decoded client payload from the PAYMENT-SIGNATURE header |
paymentPayload.x402Version | number | Protocol version (2) |
paymentPayload.resource | object | (Optional) Resource info echoed from the 402 response |
paymentPayload.accepted | object | The payment method the client selected |
paymentPayload.accepted.scheme | string | Payment scheme (for example, exact) |
paymentPayload.accepted.network | string | CAIP-2 network identifier |
paymentPayload.accepted.amount | string | Payment amount in raw token units |
paymentPayload.accepted.asset | string | ERC-20 token contract address |
paymentPayload.accepted.payTo | string | Recipient address |
paymentPayload.accepted.maxTimeoutSeconds | number | Maximum time the payment is valid |
paymentPayload.accepted.extra | object | Scheme-specific metadata |
paymentPayload.payload | object | Signed authorization data |
paymentPayload.payload.signature | string | Single 65-byte hex-encoded signature |
paymentPayload.payload.authorization | object | EIP-2612 permit fields |
paymentPayload.payload.authorization.from | string | Payer address |
paymentPayload.payload.authorization.to | string | Spender address (for EIP-2612 flows: the settlement wallet; for Permit2 flows: the x402ExactPermit2Proxy) |
paymentPayload.payload.authorization.value | string | Payment amount in raw token units |
paymentPayload.payload.authorization.validAfter | string | Earliest valid timestamp |
paymentPayload.payload.authorization.validBefore | string | Latest valid timestamp |
paymentPayload.payload.authorization.nonce | string | Unique nonce for replay protection |
paymentPayload.extensions | object | (Optional) Protocol extensions data |
paymentRequirements | object | Expected payment parameters from the server config |
paymentRequirements.scheme | string | Payment scheme (for example, exact) |
paymentRequirements.network | string | CAIP-2 network identifier |
paymentRequirements.amount | string | Required amount in raw token units |
paymentRequirements.asset | string | ERC-20 token contract address |
paymentRequirements.payTo | string | Expected recipient address |
paymentRequirements.maxTimeoutSeconds | number | Maximum time the payment is valid |
paymentRequirements.extra | object | Token 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.
| Field | Type | Description |
|---|---|---|
isValid | boolean | true if the payment signature and balance check pass |
payer | string | Address of the payer |
invalidReason | string | (Optional) Machine-readable reason for failure if isValid is false |
invalidMessage | string | (Optional) Human-readable failure message |
extensions | object | (Optional) Protocol extensions data |
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" }
}
}'{ "isValid": true, "payer": "{{PAYER_ADDRESS}}" }{ "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.
| Field | Type | Description |
|---|---|---|
success | boolean | true if settlement completed |
transaction | string | On-chain transaction hash (empty string if failed) |
network | string | CAIP-2 network identifier where settlement occurred |
payer | string | Address of the payer |
errorReason | string | (Optional) Machine-readable reason for failure if success is false |
errorMessage | string | (Optional) Human-readable failure message |
extensions | object | (Optional) Protocol extensions data |
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" }
}
}'{
"success": true,
"transaction": "0x...",
"network": "eip155:723487",
"payer": "{{PAYER_ADDRESS}}"
}{
"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/healthTypeScript 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
| Header | Direction | Description |
|---|---|---|
PAYMENT-REQUIRED | Response | Base64-encoded PaymentRequired object returned in a 402 response |
PAYMENT-SIGNATURE | Request | Base64-encoded payment payload sent by the client |
PAYMENT-RESPONSE | Response | Base64-encoded SettleResponse object returned on a 200 after settlement |
x-api-key | Request | API 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 status | HTTP status | Meaning |
|---|---|---|
no-payment | 402 | No PAYMENT-SIGNATURE header present |
invalid-header | 400 | Malformed PAYMENT-SIGNATURE header |
verify-failed | 402 | Signature or balance check failed |
verify-unreachable | 502 | Facilitator /verify endpoint unreachable |
settle-failed | 402 | On-chain settlement rejected |
settle-unreachable | 502 | Facilitator /settle endpoint unreachable |
settle-pending | 200 | Async settlement fired, resource served |
settled | 200 | Payment confirmed on-chain, resource served |
Facilitators supporting Radius
| Facilitator | URL | Radius networks | Notes |
|---|---|---|---|
| Radius (recommended) | Mainnet: https://facilitator.radiustech.xyz Testnet: https://facilitator.testnet.radiustech.xyz | Mainnet + Testnet | First-party, Permit2 + gas sponsoring, v2 |
| Stablecoin.xyz | https://x402.stablecoin.xyz | Mainnet + Testnet | SBC via EIP-2612, v1 and v2 |
| Middlebit | https://middlebit.com | Mainnet | Middleware layer, uses Stablecoin.xyz for settlement |
Network reference
| Property | Mainnet | Testnet |
|---|---|---|
| Chain ID | 723487 | 72344 |
| CAIP-2 | eip155:723487 | eip155:72344 |
| SBC token | 0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb | 0x33ad9e4bd16b69b5bfded37d8b5d9ff9aba014fb |
| SBC decimals | 6 | 6 |
| Permit domain name | Stable Coin | Stable Coin |
| Permit domain version | 1 | 1 |
Raw token units use 6 decimals. A value of "100" equals 0.0001 SBC. A value of "1000000" equals 1 SBC.
Troubleshooting
| Error | Cause | Action |
|---|---|---|
"No available wallets in pool" | Facilitator settlement wallets temporarily exhausted | Retry after 1-2s; contact operator if persistent |
HTTP 502 | Facilitator unreachable | Retry or switch to alternate facilitator |
"Permit expired" | Payment deadline passed | Client 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"
}