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

# Pay with x402

> Settle MoonPay Commerce payments from an AI agent using the x402 protocol. 

[x402](https://www.x402.org/) is a machine-native payment protocol built on HTTP 402 *Payment Required*. An agent makes a normal HTTP request to a paywalled endpoint, the server responds with payment instructions, the agent signs an on-chain authorization, and retries the request — the SDK handles the round-trip automatically.

MoonPay Commerce exposes two x402-paywalled endpoints: **checkout** (fixed price payments) and **deposit** (recurring or balance-based payments).

<Info>
  All payments use **real mainnet USDC**. There is no testnet x402 endpoint. Confirm the amount, chain, and recipient with the user before every payment.
</Info>

## What is x402?

x402 is a "pay-to-play" standard for HTTP APIs. Instead of an account, API key, or subscription, the client pays per request directly in stablecoins.

**Key properties:**

* **No subscription** — pay only for what you use
* **Permissionless** — no account creation, KYC, or merchant approval
* **Crypto-native** — USDC settled on Base, Ethereum, Polygon, Arbitrum, BSC, or Solana
* **Agent-friendly** — every step is a single HTTP exchange; no UI, redirects, or sessions

## How the Flow Works

```text theme={null}
1. Agent      → POST /v1/x402/checkout/<paylinkId>?payerAddress=<wallet>
2. Server     → 402 Payment Required (PAYMENT-REQUIRED header: amount, payTo, network)
3. Agent      → signs on-chain authorization (EIP-3009 / SPL token transfer)
4. Agent      → POST same URL with PAYMENT-SIGNATURE header
5. Server     → verifies + settles on-chain → 200 OK (PAYMENT-RESPONSE header: txHash)
```

Steps 2–5 are handled automatically by any x402-compatible client (the MoonPay CLI, `@x402/fetch`, `x402` Python, etc.) — application code only sees a single `POST` and the final response.

## API Endpoints

**Base URL:** `https://api.hel.io`

| Method | Path                            | Description                                       |
| ------ | ------------------------------- | ------------------------------------------------- |
| `POST` | `/v1/x402/checkout/{paylinkId}` | One-time checkout payment against a paylink       |
| `POST` | `/v1/x402/deposit/{depositId}`  | Recurring / balance-based deposit payment         |
| `GET`  | `/v1/paylink/{id}/public`       | Fetch product metadata (requires `Origin` header) |

Both `POST` endpoints return **HTTP 402** until a valid `PAYMENT-SIGNATURE` is presented.

<Warning>
  **MoonPay Commerce requires `?payerAddress=<wallet>` on every request.** The server pre-allocates a per-payer deposit wallet on the initial 402 response, so it must know the payer's address before it can respond. Standard x402 clients do not send this automatically — append it to the URL manually.

  Without it, the server returns `400` with `"x-payer-address header or ?payerAddress= query param required"`.
</Warning>

## Supported Chains

| Chain    | CAIP-2                                    | Notes                            |
| -------- | ----------------------------------------- | -------------------------------- |
| Base     | `eip155:8453`                             | Lowest gas — recommended default |
| Solana   | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Fastest settlement (\~5–10s)     |
| Ethereum | `eip155:1`                                | Higher gas                       |
| Polygon  | `eip155:137`                              | Low gas                          |
| Arbitrum | `eip155:42161`                            | Low gas                          |
| BSC      | `eip155:56`                               | Low gas                          |

When multiple chains appear in `accepts[]`, prefer **Base** unless the agent's wallet is funded elsewhere. If gas spikes beyond the per-chain threshold, that chain is excluded from `accepts[]`; if **all** chains are excluded, the server returns `400` instead of `402` — retry later.

***

## Pay with the MoonPay CLI

The fastest way to make an x402 payment is the [MoonPay CLI](https://www.npmjs.com/package/@moonpay/cli) (`mp`). It manages the wallet, signs the on-chain authorization, and handles the 402 round-trip.

### Prerequisites

* MoonPay CLI installed and authenticated — `mp wallet list` should show your wallet
* Wallet funded with mainnet USDC on the target chain
* A MoonPay Commerce paylink URL (e.g. from [moonpay.hel.io](https://moonpay.hel.io))

Check your balance before paying:

```bash theme={null}
mp token balance list --wallet <wallet-name> --chain <chain>
```

### Step 1 — Discover the product

Before paying, fetch the public paylink metadata so you can confirm the product, price, and supported chains:

```bash theme={null}
curl -s "https://api.hel.io/v1/paylink/<paylinkId>/public" \
  -H "Origin: https://app.hel.io"
```

Returns: product name, description, price (in minimal USDC units), supported blockchains.

### Step 2 — Get the payer address

```bash theme={null}
mp wallet list
```

Copy the address for the wallet you'll pay from.

### Step 3 — Confirm with the user

Restate the amount, chain, and recipient before spending real funds. For an agent integration, this is a hard requirement — never auto-execute.

> "About to send **\$10.00 USDC on Base mainnet** for *DEX Screener Token Boost* (paylink `abc123`), signed from wallet `<name>`. Proceed?"

### Step 4 — Pay

**Fixed-price paylink:**

```bash theme={null}
mp x402 request \
  --method POST \
  --url "https://api.hel.io/v1/x402/checkout/<paylinkId>?payerAddress=<YOUR_WALLET_ADDRESS>" \
  --wallet <wallet-name> \
  --chain base
```

**Dynamic-price paylink** — append `&amount=<minimalUnits>`:

```bash theme={null}
mp x402 request \
  --method POST \
  --url "https://api.hel.io/v1/x402/checkout/<paylinkId>?amount=10000000&payerAddress=<YOUR_WALLET_ADDRESS>" \
  --wallet <wallet-name> \
  --chain base
```

**Deposit payment:**

```bash theme={null}
mp x402 request \
  --method POST \
  --url "https://api.hel.io/v1/x402/deposit/<depositId>?payerAddress=<YOUR_WALLET_ADDRESS>" \
  --wallet <wallet-name> \
  --chain base
```

### Step 5 — Confirm settlement

The CLI prints the settlement transaction hash on success. Surface it to the user:

> "Paid \$10.00 USDC on Base mainnet (settle tx `0x…`). Wallet balance: \$X → \$X-10."

Settlement is **asynchronous** — the HTTP 200 returns as soon as the signature is verified, but the on-chain sweep from the per-payer deposit wallet to the merchant takes \~30–120s on mainnet.

***

## Pay with an x402 SDK

Any x402-compatible client works against MoonPay Commerce — just remember to append `?payerAddress=<wallet>` to the URL.

### Prerequisites

* Node.js, Python, or Go
* Wallet private key (loaded from environment variable — never commit)
* USDC balance on the target chain

### Install

<Tabs>
  <Tab title="Node.js / TypeScript">
    ```bash theme={null}
    # Fetch-based client (recommended)
    npm install @x402/fetch @x402/evm viem

    # For Solana support
    npm install @x402/svm @solana/kit
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    # Async (httpx) — recommended
    pip install "x402[httpx]" eth_account

    # Sync (requests)
    pip install "x402[requests]" eth_account
    ```
  </Tab>

  <Tab title="Go">
    ```bash theme={null}
    go get github.com/coinbase/x402/go
    ```
  </Tab>
</Tabs>

### Make a Payment

<Tabs>
  <Tab title="Node.js (fetch)">
    ```typescript theme={null}
    import { wrapFetchWithPayment } from "@x402/fetch";
    import { x402Client } from "@x402/core/client";
    import { registerExactEvmScheme } from "@x402/evm/exact/client";
    import { privateKeyToAccount } from "viem/accounts";

    const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
    const payerAddress = signer.address;

    const client = new x402Client();
    registerExactEvmScheme(client, { signer });
    const fetchWithPayment = wrapFetchWithPayment(fetch, client);

    // MoonPay Commerce requires ?payerAddress on the URL
    const url = `https://api.hel.io/v1/x402/checkout/<paylinkId>?payerAddress=${payerAddress}`;

    const response = await fetchWithPayment(url, { method: "POST" });

    console.log("Status:", response.status);
    console.log("Settlement:", response.headers.get("PAYMENT-RESPONSE"));
    ```
  </Tab>

  <Tab title="Python (httpx)">
    ```python theme={null}
    import asyncio, os
    from eth_account import Account

    from x402 import x402Client
    from x402.http.clients import x402HttpxClient
    from x402.mechanisms.evm import EthAccountSigner
    from x402.mechanisms.evm.exact.register import register_exact_evm_client


    async def pay_paylink(paylink_id: str):
        account = Account.from_key(os.getenv("EVM_PRIVATE_KEY"))
        payer_address = account.address

        client = x402Client()
        register_exact_evm_client(client, EthAccountSigner(account))

        url = (
            f"https://api.hel.io/v1/x402/checkout/{paylink_id}"
            f"?payerAddress={payer_address}"
        )

        async with x402HttpxClient(client) as http:
            response = await http.post(url)
            await response.aread()
            print("Status:", response.status_code)
            print("Settlement:", response.headers.get("PAYMENT-RESPONSE"))


    asyncio.run(pay_paylink("<paylinkId>"))
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
        "context"
        "fmt"
        "net/http"
        "os"
        "time"

        x402 "github.com/coinbase/x402/go"
        x402http "github.com/coinbase/x402/go/http"
        evm "github.com/coinbase/x402/go/mechanisms/evm/exact/client"
        evmsigners "github.com/coinbase/x402/go/signers/evm"
    )

    func main() {
        signer, _ := evmsigners.NewClientSignerFromPrivateKey(os.Getenv("EVM_PRIVATE_KEY"))
        payerAddress := signer.Address().Hex()

        x402c := x402.Newx402Client().Register("eip155:*", evm.NewExactEvmScheme(signer))
        httpClient := x402http.WrapHTTPClientWithPayment(
            http.DefaultClient,
            x402http.Newx402HTTPClient(x402c),
        )

        url := fmt.Sprintf(
            "https://api.hel.io/v1/x402/checkout/%s?payerAddress=%s",
            "<paylinkId>", payerAddress,
        )

        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()

        req, _ := http.NewRequestWithContext(ctx, "POST", url, nil)
        resp, err := httpClient.Do(req)
        if err != nil {
            fmt.Printf("Request failed: %v\n", err)
            return
        }
        defer resp.Body.Close()

        fmt.Println("Status:", resp.Status)
        fmt.Println("Settlement:", resp.Header.Get("PAYMENT-RESPONSE"))
    }
    ```
  </Tab>
</Tabs>

### Multi-Chain Support

Register multiple schemes to pay from any supported network:

```typescript theme={null}
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import { createKeyPairSignerFromBytes } from "@solana/kit";
import { base58 } from "@scure/base";

const evmSigner = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const svmSigner = await createKeyPairSignerFromBytes(
  base58.decode(process.env.SOLANA_PRIVATE_KEY!)
);

const client = new x402Client();
registerExactEvmScheme(client, { signer: evmSigner }); // Base, Ethereum, Polygon, Arbitrum, BSC
registerExactSvmScheme(client, { signer: svmSigner }); // Solana
```

The client picks the scheme that matches the chain offered in `accepts[]`. If the wallet is funded on Base only, ensure your client preference selects Base when multiple chains are offered.

***

## Manual Payment Flow (No SDK)

If an SDK isn't an option, you can drive the protocol with raw HTTP calls.

### Step 1 — Initial request

```bash theme={null}
curl -i -X POST \
  "https://api.hel.io/v1/x402/checkout/<paylinkId>?payerAddress=<YOUR_WALLET_ADDRESS>"
```

### Step 2 — Decode the 402 response

The server returns `HTTP 402` with a `PAYMENT-REQUIRED` header containing Base64-encoded JSON:

```json theme={null}
{
  "x402Version": 1,
  "resource": {
    "url": "/v1/x402/checkout/<id>",
    "description": "<product name>",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "10000000",
      "payTo": "0x<deposit-wallet>",
      "maxTimeoutSeconds": 60,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ]
}
```

Key points:

* `amount` is the **total cost** in minimal units — fees and gas are already included.
* `payTo` is a **per-payer deposit wallet**, not the merchant's address. Funds sweep to the merchant after settlement.
* Each entry in `accepts[]` is one supported chain — pick the one your wallet is funded on.

### Step 3 — Sign the on-chain authorization

* **EVM chains** — sign an [EIP-3009 `transferWithAuthorization`](https://eips.ethereum.org/EIPS/eip-3009) for `amount` USDC to `payTo`.
* **Solana** — build an SPL token transfer of `amount` USDC to `payTo`.

### Step 4 — Retry with the signed proof

```bash theme={null}
curl -i -X POST \
  "https://api.hel.io/v1/x402/checkout/<paylinkId>?payerAddress=<YOUR_WALLET_ADDRESS>" \
  -H "PAYMENT-SIGNATURE: <base64-encoded-signed-payload>"
```

### Step 5 — Read the settlement receipt

The `200 OK` response includes a `PAYMENT-RESPONSE` header with the on-chain settlement transaction hash:

```text theme={null}
PAYMENT-RESPONSE: { "txHash": "0x…", "payer": "0x…", "network": "eip155:8453" }
```

***

## Error Handling

| HTTP    | Code / Condition            | What to do                                                      |
| ------- | --------------------------- | --------------------------------------------------------------- |
| **402** | No payment signature        | Decode `PAYMENT-REQUIRED`, sign, retry with `PAYMENT-SIGNATURE` |
| **400** | Missing `payerAddress`      | Append `?payerAddress=<wallet>` to the URL                      |
| **400** | `PAYLINK_INACTIVE`          | Paylink disabled — do not retry                                 |
| **400** | `PAYLINK_DELETED`           | Paylink deleted — do not retry                                  |
| **400** | `X402_UNSUPPORTED_FEATURES` | Paylink requires customer detail fields; not payable via x402   |
| **400** | `FEE_MARGIN_INSUFFICIENT`   | Gas exceeds fee margin on all chains; retry later               |
| **400** | `FEE_RATE_EXCEEDS_PRICE`    | Fee rate exceeds payment price; surface to user                 |
| **400** | Empty `accepts[]`           | All chains circuit-broken on gas; retry later                   |
| **400** | Invalid payer address       | Validate format: `0x…` for EVM, base58 for Solana               |
| **400** | Invalid `amount` param      | Must be a positive integer string in minimal units              |
| **403** | `PAYLINK_SANCTIONED`        | Access restricted; do not retry                                 |
| **403** | Payer mismatch              | Signed payer doesn't match provisioned identity; verify wallet  |
| **404** | Paylink not found           | Verify the paylink ID is correct for production                 |
| **409** | Settlement in progress      | A submission is already settling; do not re-submit              |
| **429** | Rate limited                | Back off — limit is 10 requests / 60s per IP                    |

## Rate Limits

x402 endpoints are rate-limited per IP at **10 requests / 60 seconds**. Agents that batch payments should serialize requests and back off on `429`.

## Troubleshooting

**`x-payer-address header or ?payerAddress= query param required` (400)** The `payerAddress` query parameter is missing. This is required by MoonPay Commerce and is non-standard for x402 — most SDKs do not send it. Always include `?payerAddress=<wallet>` in the URL.

**Payment verification failed / on-chain revert** The payer wallet has no USDC on the network the server allocated the deposit wallet for. Check `mp token balance list --wallet <name> --chain <chain>` — `accepts[].network` in the 402 response tells you which chain to fund.

**All chains return 400 instead of 402** Gas spiked across all supported chains and the circuit breaker excluded them all from `accepts[]`. Wait for gas to settle (typically minutes) and retry.

**409 Conflict — settlement in progress** A previous submission for this transaction is still being processed. Do not re-submit; wait 30–60s for settlement to resolve.

## Pre-Payment Checklist

Before executing any payment, verify:

* Wallet has sufficient USDC balance on the target chain
* Paylink ID is valid (confirmed via `GET /v1/paylink/{id}/public`)
* `?payerAddress=<wallet>` is included in the URL
* Amount is correct — in minimal units (6 decimals), matches the product price
* User has explicitly confirmed: product name, amount, chain, and wallet

## Resources

<CardGroup cols={2}>
  <Card title="x402 Protocol" icon="globe" href="https://www.x402.org/">
    Official protocol specification and ecosystem
  </Card>

  <Card title="x402 Quickstart for Buyers" icon="book" href="https://docs.x402.org/getting-started/quickstart-for-buyers">
    Coinbase's getting-started guide for x402 clients
  </Card>

  <Card title="MoonPay CLI" icon="terminal" href="https://www.npmjs.com/package/@moonpay/cli">
    `mp` — sign and submit x402 payments from the command line
  </Card>

  <Card title="x402 GitHub" icon="github" href="https://github.com/coinbase/x402">
    Reference clients in TypeScript, Python, and Go
  </Card>

  <Card title="Agent Payments Overview" icon="robot" href="/docs/agent-payments/overview">
    Other agent-payable protocols and the merchant integration model
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/webhooks">
    React to settled agent payments via paylink and deposit webhooks
  </Card>
</CardGroup>
