Skip to content
LogoLogo

x402 payments

How x402 payments work on Radius

View as Markdown

x402 is an HTTP-native payment protocol for paid APIs and paid content.

A protected endpoint returns 402 Payment Required with payment requirements. The client signs payment data and retries with a PAYMENT-SIGNATURE header. A facilitator verifies and settles the payment on Radius.

Radius is a strong fit for this model because fees are low and predictable.

Start with radius-sdk

Use radius-sdk when it fits your stack. It handles the challenge, signing (EIP-3009 by default, Permit2 with its approval when a seller needs it), settlement through the Radius facilitator, and receipts, with SBC and the Radius network as defaults:

  • Accept payments: charge for routes in Workers, Hono, web-standard route handlers, or frameworks with an upstream x402 adapter.
  • Make payments: pay from TypeScript in any runtime with fetch, including browsers. From a terminal or an agent shell, use radius-cli.

Build against the protocol directly only when the SDK does not fit, for example a service written in another language. radius-sdk speaks standard x402 v2, so any compliant x402 client can pay a radius-sdk endpoint, and radius-sdk can pay any x402 v2 endpoint on Radius. The rest of this page and the facilitator API reference give the values a direct integration needs.

What you can build with x402

Request lifecycle

  1. Client requests a protected resource.
  2. Server responds with 402 Payment Required and a PAYMENT-REQUIRED header listing the payments it accepts.
  3. Client signs a payment for one offer and retries with a PAYMENT-SIGNATURE header.
  4. Server sends the payment to a facilitator, which verifies it and settles it on Radius.
  5. Server returns the resource with a PAYMENT-RESPONSE header that carries the transaction hash.

The Radius payment challenge

The PAYMENT-REQUIRED header holds a Base64-encoded JSON PaymentRequired object; the response body carries no protocol data. This is the decoded challenge radius-sdk sends for a 0.1 SBC route on mainnet:

{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": "https://api.example.com/premium/report",
    "description": "Premium report",
    "mimeType": ""
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:723487",
      "amount": "100000",
      "asset": "0x33ad9e4BD16B69B5BFdED37D8B5D9fF9aba014Fb",
      "payTo": "0x{{MERCHANT_ADDRESS}}",
      "maxTimeoutSeconds": 300,
      "extra": {
        "assetTransferMethod": "eip3009",
        "name": "Stable Coin",
        "version": "1",
        "paymentFlow": "upfront"
      }
    }
  ]
}
FieldMeaning
networkCAIP-2 chain: eip155:723487 (mainnet) or eip155:72344 (testnet)
amountBase units; SBC has six decimals, so "100000" is 0.1 SBC
extra.assetTransferMethodeip3009: the payer signs an EIP-3009 transfer authorization for the exact amount; no approval is needed
extra.name, extra.versionThe SBC EIP-712 domain, used to sign the authorization
extra.paymentFlowSet by radius-sdk: upfront settles before the handler runs; a server-side hint that clients do not need to echo

The client answers with a PAYMENT-SIGNATURE header: a Base64-encoded PaymentPayload that echoes the chosen offer in accepted and carries an EIP-3009 TransferWithAuthorization signature under payload.authorization, from the payer to payTo for exactly amount. The facilitator submits it to the token and pays the gas, so the payer needs only SBC and sends no transaction.

Permit2 offers

A challenge can instead offer "assetTransferMethod": "permit2". That happens for the upto scheme, which is Permit2-only because the facilitator charges actual usage up to a signed maximum, and with a facilitator that does not list eip3009 first for the network. The payer then signs a Permit2 permitWitnessTransferFrom, whose spender is the canonical x402ExactPermit2Proxy (0x402085c248EeA27D92E8b30b2C58ed07f9E20001), which only transfers to the payTo address. Permit2 needs a one-time SBC allowance. When the facilitator sponsors it, the challenge declares extensions.eip2612GasSponsoring, and a payer that has not approved Permit2 yet adds an EIP-2612 permit under that key in the payload instead of sending an approval transaction. radius-sdk declares the extension only on challenges that carry a Permit2 offer.

See the x402 exact EVM scheme spec for every field, and the x402 facilitator API for /verify and /settle.

Choose a facilitator

DetailValue
URL (mainnet)https://facilitator.radiustech.xyz
URL (testnet)https://facilitator.testnet.radiustech.xyz
NetworksRadius mainnet (eip155:723487), testnet (eip155:72344)
TokenSBC via EIP-3009; Permit2 with EIP-2612 gas sponsoring
Protocolx402 v2
OperatorRadius (first-party)

The Radius facilitator lists eip3009 before permit2 in /supported, so radius-sdk sellers offer EIP-3009. It settles each payment in one transaction and pays all gas. It exposes /supported, /verify, /settle, and /health; it is the default in radius-sdk. Integrators do not need to know its settlement wallet addresses. Query the endpoint you plan to use before deployment:

curl https://facilitator.radiustech.xyz/supported
curl https://facilitator.testnet.radiustech.xyz/supported

Other facilitators

FacilitatorNetworksTransfer method
Stablecoin.xyzMainnet and testneterc2612 (permit + transferFrom)
MiddlebitMainnetRoutes through Stablecoin.xyz

erc2612 is Stablecoin.xyz's own transfer method, not part of the x402 exact EVM scheme, which defines eip3009 and permit2. radius-sdk, radius-cli, and other standard x402 clients cannot pay it; payers need a client that supports Stablecoin.xyz's format. They are therefore not failover targets for payments made with radius-sdk or radius-cli. See the Stablecoin.xyz x402 documentation to integrate with it.

To use your own facilitator with radiusPayments, pass facilitator: { url, apiKey } for one that serves the x402 facilitator API, or a FacilitatorClient from @x402/core/server.

SBC and transfer methods

The token determines which x402 transfer methods are available:

StandardUSDC (FiatTokenV2_2)SBC (Radius native)Integration impact
EIP-2612 (permit)✅✅Gasless approvals, used for Permit2 gas sponsoring
EIP-3009 (transferWithAuthorization)✅✅The eip3009 transfer method, the default on Radius
EIP-1271 (contract-wallet signature validation)✅❌Smart-account compatibility is reduced for signed payments

Radius payments use EIP-3009: each payment is one signed authorization for its exact amount, settled in one transaction, with no allowance. Permit2 remains for the upto scheme and for facilitators that do not offer EIP-3009. There, the payer grants Permit2 an allowance once, through a gas-sponsored EIP-2612 permit or an approval transaction, and each payment is a separate Permit2 signature capped to its amount.

Check settlement and delivery

radius-sdk performs these checks for you. If you verify payments yourself, check the payment against your own requirements before settling:

  • scheme, network, asset, and payTo match what you offered
  • amount is at least the price, in six-decimal base units
  • the signature is valid and the validity window has not expired

After settlement, store the transaction hash with an idempotency key so a retried request does not charge twice. The PAYMENT-RESPONSE header carries the hash; to reconcile a payment on-chain, use getSettlement(radiusTestnet, txHash) (or radiusMainnet) from radius-sdk/client.

With settle-before-handler (the radius-sdk default), a payment can succeed while the resource fails to deliver. Log the transaction hash with the failure so you can serve the resource again or refund.

Recover from failed or uncertain payments

One rule applies to buyers and sellers: before authorizing another payment, find out whether the first one settled. Checking status, delivering a paid resource again, and signing a new payment are different actions.

What happenedDid money move?Safe next step
The paid retry got a new 402 (payment_rejected), or verification failedNoFix the cause, then sign a new payment
The facilitator was unreachable before settlement was requested (the seller can tell)NoRetry; another facilitator is fine if it supports the same scheme and method
The facilitator failed or timed out during settlement; the buyer sees 502 or a timeoutMaybeCheck status before anything else
The payment settled but the resource did not arriveYesDeliver the same purchase again; do not charge again

radiusPayments answers 502 with facilitator_error when the facilitator fails or cannot be reached, at verification or settlement, so a buyer must treat a 502 as uncertain. A definite settlement failure is a 402.

Check status

  • With a transaction hash, from the PAYMENT-RESPONSE header or the seller's logs: getSettlement(radiusTestnet, txHash) (or radiusMainnet) from radius-sdk/client. undefined means the node does not know the transaction yet, not that it failed.
  • Without one: the seller can check whether the payment's nonce has been used. payer and nonce are in the base64 JSON of the request's PAYMENT-SIGNATURE header; radiusPayments does not pass them to the handler, so log the header if you need this check. For an EIP-3009 payment they are payload.authorization.from and .nonce: read the token's authorizationState(payer, nonce) with viem. For a Permit2 payment they are payload.permit2Authorization.from and .nonce: use isPermit2NonceUsed(client, { owner, nonce }) from radius-sdk/client (or the permit2Actions() action). Until the authorization's deadline (validBefore for EIP-3009) passes, an unused nonce can still be settled; after it, an unused nonce means the payment did not settle. The buyer can ask the seller.

Retry or pay again

  • Submitting the same signed payment again cannot charge twice: each EIP-3009 or Permit2 nonce can be used once.
  • Signing a new payment while the first is uncertain can charge twice. Calling the payFetch from createRadiusFetch or radius-cli wallet pay again signs a new payment.
  • Switch facilitators only to one that supports the same scheme and transfer method (exact with eip3009 on Radius). Today that is the Radius facilitator; the other facilitators are not substitutes for radius-sdk payments.

Troubleshooting

"No available wallets in pool"

This error from the facilitator /settle endpoint means the facilitator's internal pool of settlement wallets is temporarily exhausted. This is a facilitator operational issue, not a client-side or payer-wallet problem.

Retry after a brief delay (1-2 seconds). If persistent, contact the facilitator operator.

502 from facilitator

The facilitator failed or could not be reached. During settlement the payment may still have settled, so check before paying again; see recover from failed or uncertain payments.

"Permit expired"

The validBefore or deadline timestamp in the payment authorization has passed. The paying client needs to re-sign with a fresh deadline.

Client refuses to pay

radius-sdk and radius-cli refuse offers before signing when the network, asset, or transfer method does not match, or the price is above the cap. The SDK's RadiusPaymentError.code names the reason, for example network_mismatch, asset_mismatch, unsupported_transfer_method, or price_above_limit.