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

# x402 Payments

> HTTP-native micropayments for agents — signed intents, sequenced off-chain, settled on Set L2.

# x402 Payments

x402 lets an agent pay for a resource **over HTTP**, using the `402 Payment Required` status code as
a protocol rather than an error. No checkout page, no human, no session.

Intents are signed off-chain, sequenced by the [sequencer](/stateset-sequencer/stateset-sequencer),
batched, and settled on [Set L2](/set/stateset-set-l2).

## The flow

```
 GET <paid resource>
   ─▶ 402 Payment Required
      { x402_version, asset, network, chain_id, amount,
        pay_to, resource, max_validity_secs, … }

 sign an X402PaymentIntent  (Ed25519, X402_PAYMENT_V1 domain)

 GET <paid resource>
      X-Payment: base64(JSON SubmitX402PaymentRequest)
   ─▶ 200 OK
      X-Payment-Receipt: base64(JSON X402PaymentReceiptHeader)
```

The intent is then sequenced, batched, committed, and settled — and the server can verify payment
later using an [inclusion proof](/set/stateset-set-l2-verification).

## Wire format

| Header              | Direction | Contents                                               |
| ------------------- | --------- | ------------------------------------------------------ |
| `X-Payment`         | request   | Standard-base64 of the JSON `SubmitX402PaymentRequest` |
| `X-Payment-Receipt` | response  | Standard-base64 of the JSON receipt header             |

<Note>
  Base64 is used because raw JSON isn't safe in an HTTP header value. The decoded document is exactly
  what `POST /api/v1/x402/payments` accepts — **snake\_case fields** — so the header path and the API
  path take the identical shape.
</Note>

## Signing

The signature is **Ed25519 over a `X402_PAYMENT_V1` domain-separated SHA-256 signing hash**.

<Warning>
  The domain separator is part of the signed input. A signature computed over the bare intent —
  without the domain prefix — will not verify. This is the same pattern as the
  [Merkle leaf hashing](/set/stateset-set-l2-verification-example#3-recompute-the-root-locally): every
  hash in the system is domain-separated.
</Warning>

The intent carries identity (`intent_id`, `x402_version`, `tenant_id`, `store_id`,
`source_agent_id`, the signing key id), the payment terms, and a validity window.

## Protocol limits

| Limit               | Value                     | Why                                                                                                                   |
| ------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Max validity window | **24 hours**              | Bounds how long a signed intent stays spendable                                                                       |
| Default batch size  | 100                       | Batching granularity for settlement                                                                                   |
| Max batch size      | 1000                      |                                                                                                                       |
| Max amount          | `i64::MAX` smallest units | Amounts persist to `BIGINT`; larger values would wrap negative and corrupt aggregation, so they're rejected at ingest |

<Tip>
  Keep `max_validity_secs` short. A signed intent is spendable until it expires, so a 24-hour window
  is a 24-hour liability if the intent leaks.
</Tip>

## Replay protection

A verified intent burns its nonce through a primary-key reservation in the nonce-tracking table.
Submitting the same intent twice fails on the second attempt rather than paying twice — which is
what makes it safe for an agent to retry a request whose response it never saw.

Header-submitted intents go through **the same code path** as the submission endpoint, so they get a
sequence number, burn their nonce, and enter the normal batching pipeline identically.

## Receipts

The `X-Payment-Receipt` header acknowledges sequencing and points at the full Merkle receipt:

```
GET /api/v1/x402/payments/:intent_id/receipt
```

That becomes available once the intent is batched. Until then the receipt header is your proof of
sequencing, not of settlement.

## Two implementations

| Path                                            | Signing                    | Header              |
| ----------------------------------------------- | -------------------------- | ------------------- |
| **Sequencer** — sequenced and settled on Set L2 | Ed25519, `X402_PAYMENT_V1` | `X-Payment`         |
| **CLI facilitator** — EVM-style exact payments  | EVM signer keys            | `payment-signature` |

<Warning>
  These are **not interchangeable**. The sequencer path is the one that batches and settles on Set L2
  with inclusion proofs. Check which side you're integrating against before implementing signing —
  the schemes and headers differ.
</Warning>

## Try it

```bash theme={null}
node examples/x402/basic_payment_flow.js
```

The CLI ships x402 helpers under `cli/src/x402/` — client, resource server, facilitator, budget, and
agent integration.

There's also an [x402 MCP server](/mcp-servers) (`stateset-x402`, 5 tools) for paid API calls from
an agent.

## Related

* [Sequencer x402](/stateset-sequencer/stateset-sequencer-x402)
* [Set L2](/set/stateset-set-l2) — where settlement lands
* [Set L2 Verification](/set/stateset-set-l2-verification) — verifying payment inclusion
