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

# x402 stablecoin acquiring

> Accept USDC stablecoin payments from AI Agents through the x402 protocol: a complete integration guide covering both merchant-hosted 402 and Waffo-hosted modes.

x402 is an open payment protocol built on the HTTP 402 status code: the server responds to an unpaid request with a 402 challenge, and the payer (typically an AI Agent) retries the request with an on-chain stablecoin authorization signature to complete payment and access the resource. Waffo packages x402 as a standard acquiring capability — orders, webhooks, and reconciliation work exactly like your existing Waffo integration, while Waffo handles on-chain verification and settlement.

This page serves two audiences: **merchant developers** (choosing an integration mode, calling Waffo APIs, deciding when to release resources) as the primary readers; **AI Agent developers** can focus on the 402 challenge structure and how to submit `PAYMENT-SIGNATURE` — the Agent-side interaction requires no merchant API key.

## Two integration modes

> **Mode 1: Merchant-hosted 402**
>
> Your server acts as the x402 server: it returns the 402 challenge to the Agent, receives the signature, then forwards it to Waffo for settlement through `order/create`. Suits merchants who want the Agent to stay on their own API end to end and are willing to implement the x402 protocol details.

> **Mode 2: Waffo-hosted 402**
>
> You only create the order and hand the Waffo-hosted URL to the Agent; the 402 challenge, signature collection, and settlement all happen on the Waffo cashier. Suits merchants who want to go live quickly without touching x402 protocol details.

Both modes share the same [`order/create`](/docs/api-reference/order-create/create-new-order) endpoint. The only differentiator is whether the request's `x402Info` **contains `paymentSignatureHeader`** — with a signature it is Mode 1, without one it is Mode 2.

## Prerequisites

> [!NOTE]
>
> x402 acquiring requires two provisioning steps, both arranged through the Waffo integration team:
>
> 1. Enable the **CRYPTO / USDC payment product** in your merchant contract
> 2. Configure your **on-chain receiving parameters** on the Waffo side
>
> Until provisioning is complete, `wallet/inquiry` and `order/create` return error code `A0010` (operation not allowed in merchant contract).

## Current scope

> [!WARNING]
>
> The following are hard boundaries of the current version — build against them and do not assume other capabilities:
>
> * **Currency**: `USDC` only; `orderCurrency` must be `USDC` (same-currency acquiring, no FX involved)
> * **Network**: Base only (`eip155:8453` in production; Base Sepolia `eip155:84532` in sandbox)
> * **Scheme**: `exact` only (settles exactly the amount in the challenge)
> * **Authorization count**: an EIP-3009 authorization is consumed once on-chain — one signature settles once; "authorize once, charge many times" is not supported
> * **Idempotency**: `paymentRequestId` is required; repeated `order/create` calls for the same order return the existing order's real status without settling again (see [Idempotency](/docs/en/developer-docs/core-concepts/idempotency))

## Fetching on-chain receiving parameters

Before constructing the 402 challenge in Mode 1, call [`POST /api/v1/wallet/inquiry`](/docs/api-reference/wallet-inquiry/wallet-inquiry) to fetch the receiving parameters. This endpoint is query-only: it creates no order and validates no amount.

```bash Request theme={null}
curl -X POST https://api-sandbox.waffo.com/api/v1/wallet/inquiry \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "X-SIGNATURE: YOUR_RSA_SIGNATURE" \
  -d '{
    "paymentRequestId": "x402-order-10001",
    "merchantInfo": { "merchantId": "M000001" },
    "orderCurrency": "USDC"
  }'
```

```json Response theme={null}
{
  "code": "0",
  "msg": "Success",
  "data": {
    "network": "eip155:84532",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "payTo": "0xc15Ea3D0b7A29c41F8b26aD5c30F49E20e510e71"
  }
}
```

| Field | Description |
| - | - |
| `network` | CAIP-2 network identifier. Sandbox returns Base Sepolia `eip155:84532`; production returns Base mainnet `eip155:8453` |
| `asset` | USDC token contract address (varies by environment and network; Base mainnet in production is `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) |
| `payTo` | Waffo platform receiving address. Waffo receives on-chain and settles to you per your contract |

> [!NOTE]
>
> All three fields come from Waffo configuration — use them verbatim when constructing the challenge. Do not replace `payTo` with your own wallet address: the receiving address and amount inside the Agent's signature are strictly verified before settlement.

## Mode 1: Merchant-hosted 402

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant A as AI Agent
    participant M as Merchant server
    participant W as Waffo
    participant B as Base chain

    A->>M: Request paid resource
    M->>W: POST /api/v1/wallet/inquiry
    W-->>M: network / asset / payTo
    M-->>A: HTTP 402 (challenge in JSON body)
    A->>A: Sign EIP-3009 authorization
    A->>M: Retry original request with PAYMENT-SIGNATURE
    M->>W: POST /api/v1/order/create (x402Info.paymentSignatureHeader)
    W->>B: verify + settle (on-chain transfer)
    B-->>W: Transaction result (txHash)
    W-->>M: orderStatus (with x402Response on PAY_SUCCESS)
    alt orderStatus = PAY_SUCCESS
        M-->>A: 200 + resource + PAYMENT-RESPONSE
    else orderStatus = PAY_IN_PROGRESS
        M-->>A: 202 processing, release after webhook / inquiry converges
    else orderStatus = ORDER_CLOSE
        M-->>A: Payment failed, do not release resource
    end
```

You need to do four things:

1. Call `wallet/inquiry` for `network` / `asset` / `payTo`, then build the 402 challenge from your pricing (amount in atomic units, `scheme` fixed to `exact`) and return it to the Agent
2. Take the signature from the Agent's retry request header `PAYMENT-SIGNATURE` and place it **as-is** into `x402Info.paymentSignatureHeader` of `order/create` (do not decode and reassemble it)
3. Decide on release from the synchronous response's `orderStatus`: release on `PAY_SUCCESS`, wait for the [webhook](/docs/en/developer-docs/webhook/overview) or [`order/inquiry`](/docs/api-reference/order-inquiry/order-inquiry) final state on `PAY_IN_PROGRESS`, reject on `ORDER_CLOSE`
4. When releasing, return the response's `x402Info.x402Response` to the Agent as the `PAYMENT-RESPONSE` header — the x402-compliant settlement receipt

```bash Request theme={null}
curl -X POST https://api-sandbox.waffo.com/api/v1/order/create \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "X-SIGNATURE: YOUR_RSA_SIGNATURE" \
  -d '{
    "paymentRequestId": "x402-order-10001",
    "merchantOrderId": "x402-order-10001",
    "orderCurrency": "USDC",
    "orderAmount": "9.99",
    "orderDescription": "API usage credits",
    "notifyUrl": "https://merchant.example.com/webhook/waffo",
    "merchantInfo": { "merchantId": "M000001" },
    "userInfo": { "userId": "agent_7f3e", "userTerminal": "WEB" },
    "paymentInfo": { "productName": "ONE_TIME_PAYMENT", "payMethodName": "USDC" },
    "x402Info": {
      "x402Request": true,
      "paymentSignatureHeader": "eyJ4NDAyVmVyc2lvbiI6Miwic2NoZW1lIjoiZXhhY3QiLC4uLn0="
    }
  }'
```

```json Response (settled) theme={null}
{
  "code": "0",
  "msg": "Success",
  "data": {
    "paymentRequestId": "x402-order-10001",
    "merchantOrderId": "x402-order-10001",
    "acquiringOrderId": "A202607060001",
    "orderStatus": "PAY_SUCCESS",
    "x402Info": {
      "x402Request": true,
      "paymentSignatureHeader": "eyJ4NDAyVmVyc2lvbiI6Miwic2NoZW1lIjoiZXhhY3QiLC4uLn0=",
      "x402Response": "eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4Li4uIn0="
    }
  }
}
```

The remaining required fields (`goodsInfo`, `orderRequestedAt`, and so on) are identical to a regular one-time payment order — refer to the [API Reference](/docs/api-reference/introduction).

`paymentSignatureHeader` is a base64-encoded x402 PaymentPayload. Decoded, it looks like this (generated by the Agent-side x402 SDK — merchants never construct it):

```json theme={null}
{
  "x402Version": 2,
  "scheme": "exact",
  "network": "eip155:84532",
  "payload": {
    "signature": "0x<65-byte EIP-712 signature>",
    "authorization": {
      "from": "0x<Agent wallet address>",
      "to": "0xc15Ea3D0b7A29c41F8b26aD5c30F49E20e510e71",
      "value": "9990000",
      "validAfter": "0",
      "validBefore": "1783340000",
      "nonce": "0x<32-byte random nonce (anti-replay, non-sequential)>"
    }
  }
}
```

> [!WARNING]
>
> `authorization.to` in the signature must equal the `payTo` returned by `wallet/inquiry`, and `authorization.value` must equal the atomic-unit representation of `orderAmount` (see "Amounts and decimals" below). Any mismatch fails verification before settlement and the order moves to `ORDER_CLOSE`. The price you quote to the Agent in the 402 challenge must therefore match the `orderAmount` of `order/create`.

`x402Response` is the base64-encoded settlement receipt (x402 SettlementResponse), returned only when `orderStatus = PAY_SUCCESS`. Decoded:

```json theme={null}
{
  "success": true,
  "transaction": "0x8f4e2b7c9d1a4f6e8b3c5d7a9e1f2b4c6d8e0a2c4e6f8a0b2c4d6e8f0a1b3c5d",
  "network": "eip155:84532",
  "payer": "0x51E2aF03D0A5c3C7dF83Db9F4b0a2E51D8c19aC3"
}
```

`transaction` is the on-chain transaction hash, usable for reconciliation and on-chain verification; `payer` is the Agent's paying wallet address.

## Mode 2: Waffo-hosted 402

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant A as AI Agent
    participant M as Merchant server
    participant W as Waffo API
    participant C as Waffo cashier

    A->>M: Request paid resource
    M->>W: POST /api/v1/order/create (x402Request=true, no signature)
    W-->>M: AUTHORIZATION_REQUIRED + orderAction.webUrl
    M-->>A: HTTP 302 Location: webUrl
    A->>C: GET webUrl (hosted x402 endpoint)
    C-->>A: HTTP 402 (challenge in JSON body)
    A->>A: Sign EIP-3009 authorization
    A->>C: Retry same URL with PAYMENT-SIGNATURE
    C->>W: Submit signature, trigger on-chain verify + settle
    W-->>C: orderStatus
    C-->>A: HTTP 302 (successRedirectUrl / failedRedirectUrl)
    W--)M: Webhook with final status (x402Response on PAY_SUCCESS)
    M-->>A: Release resource after confirming PAY_SUCCESS
```

You need to do three things:

1. Call `order/create` with `x402Info: { "x402Request": true }` (no signature), plus `successRedirectUrl` / `failedRedirectUrl`
2. Parse `orderAction` (a JSON string) from the response, take `webUrl`, and 302-redirect the Agent there; the 402 challenge, signature collection, and settlement all happen on the Waffo cashier from that point
3. Release resources based on the `orderStatus` from the [webhook](/docs/en/developer-docs/webhook/overview) or `order/inquiry`

```bash Request theme={null}
curl -X POST https://api-sandbox.waffo.com/api/v1/order/create \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "X-SIGNATURE: YOUR_RSA_SIGNATURE" \
  -d '{
    "paymentRequestId": "x402-order-10002",
    "merchantOrderId": "x402-order-10002",
    "orderCurrency": "USDC",
    "orderAmount": "9.99",
    "orderDescription": "API usage credits",
    "notifyUrl": "https://merchant.example.com/webhook/waffo",
    "successRedirectUrl": "https://merchant.example.com/x402/return",
    "failedRedirectUrl": "https://merchant.example.com/x402/failed",
    "merchantInfo": { "merchantId": "M000001" },
    "userInfo": { "userId": "agent_7f3e", "userTerminal": "WEB" },
    "paymentInfo": { "productName": "ONE_TIME_PAYMENT", "payMethodName": "USDC" },
    "x402Info": { "x402Request": true }
  }'
```

```json Response theme={null}
{
  "code": "0",
  "msg": "Success",
  "data": {
    "paymentRequestId": "x402-order-10002",
    "merchantOrderId": "x402-order-10002",
    "acquiringOrderId": "A202607060002",
    "orderStatus": "AUTHORIZATION_REQUIRED",
    "orderAction": "{\"actionType\":\"WEB\",\"webUrl\":\"https://cashier.waffo.com/cashier/api/v1/x402/7sKq9WfR2mXt\"}"
  }
}
```

When the Agent visits `webUrl`, it receives HTTP 402 with the challenge in the **response body** (the x402 protocol places the challenge in the body — there is no dedicated response header):

```json theme={null}
{
  "x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:84532",
      "maxAmountRequired": "9990000",
      "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "payTo": "0xc15Ea3D0b7A29c41F8b26aD5c30F49E20e510e71",
      "maxTimeoutSeconds": 600,
      "extra": { "name": "USDC", "version": "2" }
    }
  ]
}
```

After signing, the Agent retries the same URL using **the same HTTP method as the first visit**, carrying the signature in the `PAYMENT-SIGNATURE` request header (corresponding to `X-PAYMENT` in the x402 spec) — no merchant credentials required:

```bash theme={null}
curl "https://cashier.waffo.com/cashier/api/v1/x402/7sKq9WfR2mXt" \
  -H "PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6Miwic2NoZW1lIjoiZXhhY3QiLC4uLn0="
```

After settlement the cashier 302-redirects the Agent back to your `successRedirectUrl` or `failedRedirectUrl`. The hosted URL serves both browsers (renders a checkout page) and x402 clients (returns protocol responses).

> [!WARNING]
>
> The 302 redirect is only flow plumbing — **it is not proof of payment**. Treat an order as paid only when the webhook notification or an `order/inquiry` query shows `orderStatus = PAY_SUCCESS`. Redirects can be forged or lost, and settlement may not have finalized when the redirect fires.

On success, the webhook notification and the `order/inquiry` response carry `x402Info.x402Response` (same structure as Mode 1); the rest of the notification is identical to a regular order — see [Webhook event types](/docs/en/developer-docs/webhook/event-types).

## Mode comparison

| Dimension | Mode 1: Merchant-hosted 402 | Mode 2: Waffo-hosted 402 |
| - | - | - |
| Who acts as the x402 server (issues 402, receives signature) | Merchant server | Waffo cashier |
| Must implement x402 protocol details | Yes (build challenge, parse signature header) | No |
| `order/create` carries a signature | Yes (`x402Info.paymentSignatureHeader`) | No |
| Create response | Final status directly, or `PAY_IN_PROGRESS` | `AUTHORIZATION_REQUIRED` + hosted URL |
| Redirects | None (Agent talks to merchant API throughout) | Merchant 302 → cashier 302 back |
| Basis for releasing resources | `orderStatus` from sync response / webhook / inquiry | `orderStatus` from webhook / inquiry |
| Best for | Merchants building their own x402 service with deep customization | Merchants shipping fast without touching protocol details |

Order status semantics (`PAY_IN_PROGRESS`, `AUTHORIZATION_REQUIRED`, and so on) match other payment methods — see [Payment lifecycle](/docs/en/developer-docs/core-concepts/payment-lifecycle).

## Amounts and decimals (two layers of units)

The same transaction carries amounts in two layers — do not mix them:

| Layer | Field | Representation | Example (9.99 USDC) |
| - | - | - | - |
| Waffo API layer | `orderAmount` | String, up to 2 decimal places | `"9.99"` |
| On-chain / x402 protocol layer | `value`, `maxAmountRequired` | String, atomic-unit integer (USDC has 6 on-chain decimals) | `"9990000"` |

Conversion: atomic units = amount × 10<sup>6</sup>. The 2 decimal places for USDC in [Currency and amounts](/docs/en/developer-docs/core-concepts/currency) refer to the Waffo API layer's `orderAmount`; amounts in the 402 challenge and the EIP-3009 signature are always atomic units. In Mode 1 both layers must represent the same value, or settlement verification fails.

## Error codes

x402 reuses Waffo's existing external error codes; `msg` explains the specific reason:

| Code | Meaning | Common x402 triggers |
| - | - | - |
| `A0003` | Parameter validation failed | The challenge / signature scheme is not `exact`; `paymentRequestId` missing |
| `A0007` | Transaction currency is not supported | `orderCurrency` is not `USDC`, or no usable same-currency USDC contract |
| `A0010` | Operation not allowed in merchant contract | CRYPTO/USDC payment product not enabled, or on-chain receiving parameters not configured |

See the full list in the [Error codes reference](/docs/en/developer-docs/tools-and-references/developer-tools/error-codes).

## FAQ

> **Which chain does the sandbox use?**
>
> The sandbox runs on the Base Sepolia testnet (`eip155:84532`) with USDC contract `0x036CbD53842c5426634e7929541eC2318f3dCF7e`; production runs on Base mainnet (`eip155:8453`) with USDC contract `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`. In both environments, always use the `network` / `asset` / `payTo` actually returned by `wallet/inquiry`.

> **Are other currencies or networks supported?**
>
> Currently only USDC on the Base network. Passing any other `orderCurrency` returns `A0007`.

> **Can one authorization be charged multiple times?**
>
> No. The x402 `exact` scheme is built on EIP-3009 `transferWithAuthorization`, whose nonce is consumed once on the USDC contract: once a signature settles it is void, and resubmitting it fails on-chain. Subscription-style or metered repeat charging is outside the scope of x402.

> **orderStatus is PAY\_IN\_PROGRESS — can I release the resource early?**
>
> No. `PAY_IN_PROGRESS` only means on-chain processing is underway; it may still succeed or fail. Release resources only on `orderStatus = PAY_SUCCESS`, converged through the webhook or `order/inquiry`.

> **Is the 402 challenge in a response header or the body?**
>
> In the response body (JSON). There is no header carrying the challenge. The only protocol request header in the x402 interaction is `PAYMENT-SIGNATURE`, used by the Agent to submit the signature (corresponding to `X-PAYMENT` in the x402 spec); the settlement receipt is returned through the `x402Info.x402Response` field, corresponding to the spec's `X-PAYMENT-RESPONSE`.

> **Is this the same thing as the AI integration tool (waffo-integrate)?**
>
> No. The [AI integration tool](/docs/en/developer-docs/integration/ai-integration) uses AI coding tools to generate your Waffo SDK integration code; this page is about AI Agents **paying you** in stablecoin for your services. They can be used together but solve different problems.

## Further reading

* [Payment lifecycle](/docs/en/developer-docs/core-concepts/payment-lifecycle): order state machine and final-state convergence
* [Currency and amounts](/docs/en/developer-docs/core-concepts/currency): amount formats and decimal rules
* [Idempotency](/docs/en/developer-docs/core-concepts/idempotency): idempotency semantics of `paymentRequestId`
* [Webhook overview](/docs/en/developer-docs/webhook/overview): signature verification and retry policy
* [API Reference](/docs/api-reference/introduction): full field definitions for [`order/create`](/docs/api-reference/order-create/create-new-order), [`order/inquiry`](/docs/api-reference/order-inquiry/order-inquiry), and [`wallet/inquiry`](/docs/api-reference/wallet-inquiry/wallet-inquiry)


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