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

<CardGroup cols={2}>
  <Card title="Mode 1: Merchant-hosted 402" icon="server">
    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.
  </Card>

  <Card title="Mode 2: Waffo-hosted 402" icon="cloud">
    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.
  </Card>
</CardGroup>

Both modes share the same [`order/create`](/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).
</Note>

## 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](/en/developer-docs/core-concepts/idempotency))
</Warning>

## Fetching on-chain receiving parameters

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

<CodeGroup>
  ```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"
    }
  }
  ```
</CodeGroup>

| 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.
</Note>

## 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](/en/developer-docs/webhook/overview) or [`order/inquiry`](/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

<CodeGroup>
  ```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="
      }
    }
  }
  ```
</CodeGroup>

The remaining required fields (`goodsInfo`, `orderRequestedAt`, and so on) are identical to a regular one-time payment order — refer to the [API Reference](/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`.
</Warning>

`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](/en/developer-docs/webhook/overview) or `order/inquiry`

<CodeGroup>
  ```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\"}"
    }
  }
  ```
</CodeGroup>

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.
</Warning>

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](/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](/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](/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](/en/developer-docs/tools-and-references/developer-tools/error-codes).

## FAQ

<AccordionGroup>
  <Accordion title="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`.
  </Accordion>

  <Accordion title="Are other currencies or networks supported?">
    Currently only USDC on the Base network. Passing any other `orderCurrency` returns `A0007`.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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`.
  </Accordion>

  <Accordion title="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`.
  </Accordion>

  <Accordion title="Is this the same thing as the AI integration tool (waffo-integrate)?">
    No. The [AI integration tool](/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.
  </Accordion>
</AccordionGroup>

## Further reading

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