# x402 payments

*How x402 payments work on Radius*

[x402](https://www.x402.org/) 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`](/reference/radius-sdk.md) 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](/build/accept-payments.md):** charge for routes in Workers, Hono, web-standard route handlers, or frameworks with an upstream x402 adapter.
* **[Make payments](/build/make-payments.md):** pay from TypeScript in any runtime with `fetch`, including browsers. From a terminal or an agent shell, use [`radius-cli`](/reference/radius-cli.md).

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](/reference/facilitator-api.md) give the values a direct integration needs.

## What you can build with x402

* **[Per-request API billing](/build/examples/real-time-api-metering.md):** charge per call with immediate settlement
* **[Content access](/build/examples/pay-per-visit-content.md):** charge for a single article, feed, or download
* **[Streaming payments](/build/examples/streaming-payments.md):** combine x402 with recurring payment loops for compute and inference workloads

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

```json
{
  "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"
      }
    }
  ]
}
```

| Field                         | Meaning                                                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `network`                     | CAIP-2 chain: `eip155:723487` (mainnet) or `eip155:72344` (testnet)                                                 |
| `amount`                      | Base units; SBC has six decimals, so `"100000"` is 0.1 SBC                                                          |
| `extra.assetTransferMethod`   | `eip3009`: the payer signs an EIP-3009 transfer authorization for the exact amount; no approval is needed           |
| `extra.name`, `extra.version` | The SBC EIP-712 domain, used to sign the authorization                                                              |
| `extra.paymentFlow`           | Set 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`](/reference/contract-addresses.md#x402-contracts) (`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](https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md) for every field, and the [x402 facilitator API](/reference/facilitator-api.md) for `/verify` and `/settle`.

## Choose a facilitator

### Radius (recommended)

| Detail        | Value                                                      |
| ------------- | ---------------------------------------------------------- |
| URL (mainnet) | `https://facilitator.radiustech.xyz`                       |
| URL (testnet) | `https://facilitator.testnet.radiustech.xyz`               |
| Networks      | Radius mainnet (`eip155:723487`), testnet (`eip155:72344`) |
| Token         | SBC via EIP-3009; Permit2 with EIP-2612 gas sponsoring     |
| Protocol      | x402 v2                                                    |
| Operator      | Radius (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:

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

### Other facilitators

| Facilitator                                                 | Networks            | Transfer method                     |
| ----------------------------------------------------------- | ------------------- | ----------------------------------- |
| [Stablecoin.xyz](https://docs.stablecoin.xyz/x402/overview) | Mainnet and testnet | `erc2612` (permit + `transferFrom`) |
| [Middlebit](https://middlebit.com)                          | Mainnet             | Routes 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](https://docs.stablecoin.xyz/x402/overview) to integrate with it.

To use your own facilitator with `radiusPayments`, pass `facilitator: { url, apiKey }` for one that serves the [x402 facilitator API](/reference/facilitator-api.md), or a `FacilitatorClient` from `@x402/core/server`.

## SBC and transfer methods

The token determines which x402 transfer methods are available:

| Standard                                        | USDC (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 happened                                                                            | Did money move? | Safe next step                                                               |
| ---------------------------------------------------------------------------------------- | --------------- | ---------------------------------------------------------------------------- |
| The paid retry got a new `402` (`payment_rejected`), or verification failed              | No              | Fix the cause, then sign a new payment                                       |
| The facilitator was unreachable before settlement was requested (the seller can tell)    | No              | Retry; 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 timeout | Maybe           | Check status before anything else                                            |
| The payment settled but the resource did not arrive                                      | Yes             | Deliver 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](#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](#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`.

## Related pages

* [Accept payments](/build/accept-payments.md)
* [Make payments](/build/make-payments.md)
* [radius-sdk reference](/reference/radius-sdk.md)
* [radius-cli](/reference/radius-cli.md)
* [x402 facilitator API](/reference/facilitator-api.md)
* [Contract addresses](/reference/contract-addresses.md)
* [Agent payments](/build/examples/agent-payments.md)
* [Example projects](/build/examples/workshop-playground.md)
* [Network and RPC](/reference/network.md)
