# Anatomy of a payment

Walk a single payment field by field, from the 402 challenge to the settlement receipt.

Section: Understand x402
Source: https://www.roundhouse.studio/docs/x402/anatomy-of-a-payment

---

The field-level reference. Read it beside your own bytes when a payment is failing.

## 1. The challenge

An unpaid request returns `402` with a JSON body:

```json
{
  "x402Version": 1,
  "error": "payment required",
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "10000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x1234…",
      "resource": "https://api.example.com/forecast",
      "description": "One weather forecast",
      "mimeType": "application/json",
      "maxTimeoutSeconds": 60,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ]
}
```

| Field | What it is | Why it matters |
| --- | --- | --- |
| `x402Version` | Protocol version | A client that does not know the version must not guess |
| `accepts` | Array of requirements | Several assets or chains may be offered; pick one you recognise |
| `scheme` | How payment is proved | `exact` today. An unknown scheme is a refusal, not a challenge |
| `network` | Chain identifier | Both `base` and `eip155:8453` appear in the wild — accept both |
| `maxAmountRequired` | **Atomic** units, as a string | USDC has 6 decimals: `"10000"` is $0.01, not $10,000 |
| `asset` | Token contract address | Never assume. The domain you sign over comes from this contract |
| `payTo` | Where the money goes | This is the address the index joins listings to settlements on |
| `resource` | Canonical URL being paid for | Distinguishes a service fee from forwarded value in the record |
| `maxTimeoutSeconds` | How long the server will wait | Your `validBefore` must comfortably exceed it |
| `extra` | Scheme-specific | For `exact`, the token's EIP-712 `name` and `version` |

> [!CAUTION]
> `maxAmountRequired` is atomic and a string. The single most expensive class of
> bug in x402 clients is treating it as a decimal — a client that "pays 10000
> USDC" for a one-cent resource will have the authorization rejected for
> insufficient balance if it is lucky, and settled if it is not.

## 2. The authorization

For the `exact` scheme you sign an [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009)
`TransferWithAuthorization` over the **token contract's own**
[EIP-712](https://eips.ethereum.org/EIPS/eip-712) domain:

```javascript
const authorization = {
  from: account.address,      // you
  to: requirements.payTo,     // exactly what the server asked for
  value: requirements.maxAmountRequired,
  validAfter: '0',
  validBefore: String(Math.floor(Date.now() / 1000) + 600),
  nonce: `0x${randomHex(32)}`, // 32 bytes, single-use, enforced by the token
};

const signature = await account.signTypedData({
  domain: { name: 'USD Coin', version: '2', chainId: 8453, verifyingContract: requirements.asset },
  types: {
    TransferWithAuthorization: [
      { name: 'from', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'validAfter', type: 'uint256' },
      { name: 'validBefore', type: 'uint256' },
      { name: 'nonce', type: 'bytes32' },
    ],
  },
  primaryType: 'TransferWithAuthorization',
  message: { ...authorization, value: BigInt(authorization.value), validAfter: 0n, validBefore: BigInt(authorization.validBefore) },
});
```

Four rules, each of which has cost somebody a day:

- **Sign typed data, never a message.** `personal_sign` over the JSON requirement produces a
  signature no facilitator can verify. It fails at the far end with an error that reads like a
  rejection rather than a mistake.
- **The domain comes from the token contract**, not from the server and not from a constant in your
  code. Read `extra.name` / `extra.version` and the `asset` address, or fetch a descriptor that
  states them.
- **Addresses must be EIP-55 checksummed** before anything hashes them. Typed-data libraries
  validate the checksum inside the hash function, so a lowercase address fails as *"signature does
  not authorize this transfer"* at the facilitator — a message that points nowhere near the cause.
- **`nonce` is 32 random bytes and single-use.** The token contract enforces it. Reuse is a hard
  failure, not a retry.

## 3. The X-PAYMENT header

The envelope is base64-encoded JSON:

```json
{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": { "authorization": { "from": "0x…", "to": "0x…", "value": "10000", "validAfter": "0", "validBefore": "1766000600", "nonce": "0x…" }, "signature": "0x…" }
}
```

```bash
X_PAYMENT=$(printf '%s' "$ENVELOPE_JSON" | base64 -w0)
curl -sL "https://api.example.com/forecast" -H "x-payment: $X_PAYMENT"
```

> [!WARNING]
> Base64 is the *header's* encoding, not the payload's type. When you call a
> facilitator's `/verify` or `/settle` directly, `paymentPayload` must be the
> decoded **object**. Hand it the base64 string and the reference implementation
> answers `500 No facilitator registered for x402 version: undefined`, because it
> reads `x402Version` off a value that is a string.

## 4. The receipt

Success returns the resource plus a base64 `X-PAYMENT-RESPONSE`:

```json
{
  "success": true,
  "txHash": "0xabc123…",
  "networkId": "base",
  "payer": "0x…"
}
```

Log every one of these. The transaction hash is what lets you reconcile your own spend against the
chain, and it is what turns "I think I paid" into a fact anyone can check — including
[Roundhouse](https://www.roundhouse.studio/docs/data/data-model), which will have indexed the same settlement from the other
side.

## 5. What the chain records

The settlement leaves two things behind that matter to an indexer:

- A `Transfer` log from the token: payer → payee, for the amount.
- An `AuthorizationUsed` log from the token, because the transfer went through EIP-3009 rather than
  a plain `transfer` call.

That second log is on-chain proof the transfer was an authorized payment and not an ordinary
transfer that happened to land on a known address. It is why Roundhouse can mark a settlement
verified rather than merely probable — see
[coverage and confidence](https://www.roundhouse.studio/docs/data/coverage-and-confidence).

## Next steps

- [Schemes and networks](https://www.roundhouse.studio/docs/x402/schemes-and-networks) — what is supported where
- [Errors](https://www.roundhouse.studio/docs/x402/errors) — the five failures, and which ones retrying can fix
- [Your first x402 payment](https://www.roundhouse.studio/docs/guides/first-x402-payment) — this page, done for a cent

---

Every page in these docs is available as markdown at its own URL plus `.md`.
Full index: https://www.roundhouse.studio/docs.md
