> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blankfx.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Paying a payment link with x402

> How an x402 agent pays a BlankFX payment link on Sepolia (testnet), including the one-time setup and error codes.

# Paying a payment link with x402

A merchant who has switched on **Agent payments** lets any x402 v2 agent pay their payment links. Funds move straight from the agent's wallet to the merchant's wallet. BlankFX pays the gas and records the payment.

> [!WARNING]
>
> Sepolia (testnet) and test tokens only, switched on per environment. Ethereum (mainnet) is not yet supported. This route needs no API key and is served at `https://api.blankfx.org/x402/pay/{payment-link-id}` (not under `/public/v1`).

## The flow

```
GET /x402/pay/{payment-link-id}
  → 402  + PAYMENT-REQUIRED header (base64 JSON: price, token, recipient)
GET /x402/pay/{payment-link-id}
      + PAYMENT-SIGNATURE header (the signed payment)
  → 200  + PAYMENT-RESPONSE header (success, transaction, network, payer)
```

An x402 client library does both steps for you.

## What the `402` tells the agent

| Field | Value |
| - | - |
| `scheme` / `network` | `exact` / `eip155:11155111` |
| `asset` | The invoice's token (a BlankFX Sepolia token) |
| `amount` | **Invoice amount plus the BlankFX transfer fee**, in the token's smallest unit. The payer pays the fee on top. |
| `payTo` | The merchant's payout wallet |
| `extra.assetTransferMethod` | `permit2` |

## One-time setup for the agent's wallet

**1. Approve Permit2 once**

None of the BlankFX Sepolia tokens support EIP-3009, so payment uses Permit2. Do a one-time ERC-20 `approve` of the token to the canonical Permit2 contract `0x000000000022D473030F116dDEE9F6B43aC78BA3`. The agent pays that gas itself. The approval costs roughly 136,000 gas on these tokens, so if your client sets a gas limit by hand, use at least 200,000. Without it BlankFX answers `412` (`PERMIT2_ALLOWANCE_REQUIRED`), reserves nothing, and the official client does **not** approve for you.

**2. Allow the token in your client**

The official client refuses any token missing from its built-in default list. Add the BlankFX token to `spendControls.allowedAssets`, or the payment fails with "rejected by spendControls".

**3. Raise the cap if needed**

The client caps each payment at **\$1** by default. For an allow-listed token, set `maxAmountPerPayment` on its entry.

## Minimal client

```ts theme={null}
import { wrapFetchWithPaymentFromConfig } from '@x402/fetch'
import { ExactEvmScheme } from '@x402/evm'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount(process.env.AGENT_KEY as `0x${string}`)
const pay = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: 'eip155:11155111', client: new ExactEvmScheme(account, { rpcUrl: process.env.SEPOLIA_RPC }) }],
  spendControls: {
    allowedAssets: [{ network: 'eip155:11155111', asset: '<token address from the 402>', maxAmountPerPayment: '100000000' }],
  },
})
const res = await pay('https://api.blankfx.org/x402/pay/<payment-link-id>')
```

Verified with `@x402/fetch` and `@x402/evm` v2.28.0, including a live payment on Sepolia (October 2026).

## Example: what a successful payment looks like

For a 1 USDC invoice with a 0.05% transfer fee, the `402` asks for `1000500` (6 decimals, so 1.0005 USDC). After the signed retry the response is `200` and the `PAYMENT-RESPONSE` header decodes to:

```json theme={null}
{ "success": true, "transaction": "0x…", "network": "eip155:11155111", "payer": "0x…" }
```

The body is the invoice with `paidVia: "x402"`. The merchant sees the payer and the fee in Payment Status; nobody else does.

## What BlankFX checks before it spends gas

* The recovered signer equals the payer.
* The recipient, token and exact amount equal the challenge BlankFX issued (not what the client echoes).
* The authorization has 30 to 90 seconds of validity left.
* The Permit2 nonce is unused.
* The wallet holds the amount and has approved Permit2.
* The payer's wallet is not suspended and stays within the \$2,000/day cap for unverified wallets, the same checks a human payer meets.

## Errors

| Status | Meaning |
| - | - |
| `402` with `PAYMENT-RESPONSE` `{ "success": false, "errorReason": "<code>" }` | Payment refused. A fresh `PAYMENT-REQUIRED` challenge is attached when one can be issued. `errorReason` is one of the codes below. |
| `409` `{ "error": "payment_in_progress" }` | Another payment for this link is already settling. Retry later; do not sign a new payment. |
| `409` `{ "error": "Payment request is <status>" }` | The link is cancelled or expired. |
| `412` | Permit2 allowance missing (`PERMIT2_ALLOWANCE_REQUIRED`, `errorReason: "permit2_allowance_required"`). Nothing was reserved. |
| `400` `{ "error": "invalid_payload" }` | Malformed `PAYMENT-SIGNATURE`, or a token, spender or validity window that doesn't match the challenge. |
| `404` `{ "error": "not_found" }` | The link doesn't exist, doesn't accept agent payments, or agent payments are switched off. All of these look the same on purpose. |
| `429` | Rate limited: 60 requests/min per IP, **30/min per payment link**, 20/min per payer. Honour `Retry-After`. |
| `503` `{ "error": "unavailable" }` | No challenge could be issued right now (for example the fee could not be read). Retry. |

`errorReason` codes on a `402`:

| Code | Meaning |
| - | - |
| `wrong_amount` | Amount differs from the challenge (invoice plus fee). |
| `wrong_recipient` | `to` is not the merchant's payout wallet from the challenge. |
| `insufficient_funds` | The payer's balance is below the amount. |
| `nonce_used` | The Permit2 nonce was already used. |
| `expired` | The authorization's deadline has passed. |
| `not_yet_valid` | The authorization is not valid yet. |
| `insufficient_validity` | Less than 30 seconds of validity left. Sign a fresh payment. |
| `invalid_signature` | The signature does not recover to the payer. |
| `unsupported_scheme` | Scheme or network differs from the challenge, or the transfer method is not `permit2`. |
| `kyc_required` | The payer is over the unverified cap or not permitted. |
| `payer_blocked` | The payer is temporarily blocked after repeated failed settlements. |
| `capacity` | The daily agent-payment budget for this merchant or for BlankFX is used up. |
| `invoice_changed` | The link changed (paid, cancelled) after the challenge was issued. |
| `settlement_pending` | The transaction was sent but is not confirmed yet. `PAYMENT-RESPONSE` carries the `transaction`. |
| `settlement_failed` | The settlement transaction could not be sent. |
| `fee_unavailable` | The current transfer fee could not be read. Retry. |
| `unavailable` | The challenge is unknown or expired. Request a new `402`. |

Chain error text is never returned. If settlement is still confirming, retry the **same signed payment**: it returns the current state and never sends a second transaction.

## Not supported yet

EIP-3009 tokens (none on Sepolia), converting between coins, gas-sponsored approvals, browser-based agents (no CORS on this route) and mainnet.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.