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

# Complete an x402 payment

> A full x402 payment from HTTP 402 to a verified Merkle receipt — the exact wire format, signing preimage, and replay semantics.

x402 turns HTTP 402 into a working payment protocol. You request a resource, the server tells you
what it costs, you present a signed payment intent, and you get the resource plus a receipt you can
verify against an on-chain batch commitment.

This walks the whole loop against a real sequencer.

## Prerequisites

* A payment-gated endpoint (the sequencer ships a demonstration premium route)
* An Ed25519 signing key registered in the agent key registry
* The sequencer base URL

## Step 1 — Request the resource, get a 402

```bash theme={null}
curl -i https://api.sequencer.stateset.com/api/v1/premium/resource
```

The requirements arrive in the **response body**, as a structured error whose `details` carry the
payment terms:

```json theme={null}
{
  "error": {
    "code": "PAYMENT_REQUIRED",
    "details": {
      "x402_version": 1,
      "asset": "usdc",
      "network": "set_chain",
      "chain_id": 84532001,
      "amount": 10000,
      "pay_to": "0x0000000000000000000000000000000000000402",
      "resource": "/api/v1/premium/resource",
      "max_validity_secs": 86400
    }
  }
}
```

<Warning>
  Read the terms from the body, not from a header. `amount` is in the asset's **smallest unit** —
  `10000` is 0.01 USDC at 6 decimals, not 10,000 USDC. Getting this wrong by six orders of magnitude
  is the most common first mistake.
</Warning>

## Step 2 — Build and sign the intent

The signing hash is SHA-256 over a **domain-separated, order-dependent** preimage. Every field is
concatenated in exactly this order, integers as **big-endian u64**:

```
signing_hash = SHA256(
    "X402_PAYMENT_V1"      // domain separator, ASCII
 || payer                  // ASCII address
 || payee                  // ASCII address
 || U64_BE(amount)
 || lowercase(asset)       // "usdc"
 || network                // "set_chain"
 || U64_BE(chain_id)
 || U64_BE(valid_until)    // unix seconds
 || U64_BE(nonce)
)
```

Then sign those 32 bytes with Ed25519.

```js theme={null}
import { createHash } from 'node:crypto';

const u64be = (n) => {
  const b = Buffer.alloc(8);
  b.writeBigUInt64BE(BigInt(n));
  return b;
};

function signingHash({ payer, payee, amount, asset, network, chainId, validUntil, nonce }) {
  return createHash('sha256')
    .update('X402_PAYMENT_V1')
    .update(payer)
    .update(payee)
    .update(u64be(amount))
    .update(asset.toLowerCase())
    .update(network)
    .update(u64be(chainId))
    .update(u64be(validUntil))
    .update(u64be(nonce))
    .digest();
}
```

<Warning>
  The order is part of the hash. Reordering any two fields, encoding an integer little-endian, or
  sending `USDC` where the preimage expects `usdc` all produce a different hash and the signature will
  be rejected. `valid_until` must be within `max_validity_secs` (86,400 — 24 hours) of now.
</Warning>

## Step 3 — Retry with `X-Payment`

The `X-Payment` header carries **standard base64 of the JSON document** that
`POST /api/v1/x402/payments` accepts — base64 because raw JSON is not safe in a header value.

```bash theme={null}
INTENT='{
  "tenant_id": "...", "store_id": "...", "agent_id": "...",
  "payer_address": "0x1234...", "payee_address": "0x0987...",
  "amount": 10000, "asset": "usdc", "network": "set_chain",
  "valid_until": 1705320000, "nonce": 42,
  "signing_hash": "0x...", "payer_signature": "0x..."
}'

curl -i https://api.sequencer.stateset.com/api/v1/premium/resource \
  -H "X-Payment: $(printf '%s' "$INTENT" | base64 -w0)"
```

A valid intent returns **200** with the resource, plus a receipt header:

```
X-Payment-Receipt: base64({
  "intent_id": "...",
  "status": "sequenced",
  "sequence_number": 8814,
  "sequenced_at": "2026-01-15T10:30:00Z",
  "receipt_url": "/api/v1/x402/payments/<intent_id>/receipt"
})
```

<Note>
  A gated request and a direct submission to `POST /api/v1/x402/payments` run through the **same**
  verification and sequencing path. The header route is not a shortcut — the intent gets a sequence
  number, burns its nonce, and enters the normal batching pipeline either way.
</Note>

## Replay protection, and one trap

The intent is **consumed by the request it pays for**.

* The nonce is reserved in a nonce-tracking table keyed by payer. Presenting the same signed intent
  twice fails the second time with `Nonce already used for this payer`.
* Any `idempotency_key` in the decoded intent is **discarded** on the header path, deliberately: it
  is not part of the signed hash, so honouring it would let a replay short-circuit through the
  idempotency lookup and get the resource twice for one payment.

<Warning>
  Do not build retry logic that re-presents the same `X-Payment` value. A network timeout after the
  server sequenced your intent has already spent the nonce — retrying returns a nonce error, not the
  resource. Sign a fresh intent with a new nonce, and reconcile the first one via its `receipt_url`.
</Warning>

## Step 4 — Fetch and verify the receipt

`receipt_url` becomes fully populated once the intent is **batched** — sequencing is immediate,
batching is not.

```bash theme={null}
curl https://api.sequencer.stateset.com/api/v1/x402/payments/$INTENT_ID/receipt
```

The receipt carries a Merkle inclusion proof. Verify it against the anchored batch commitment
independently of the sequencer — that verification, including the domain-separated leaf and node
hashing, is documented in
[Set L2 verification](/set/stateset-set-l2-verification-example).

<Tip>
  Verifying the proof is what makes the receipt worth having. An unverified receipt is just the
  sequencer asserting it did its job; a verified one is arithmetic anybody can check against the chain.
</Tip>

## The full loop

```
GET /premium/resource
  └─▶ 402  body.details = payment requirements
        │
        ├─ sign X402_PAYMENT_V1 preimage (Ed25519)
        ▼
GET /premium/resource   X-Payment: base64(intent)
  └─▶ 200  resource + X-Payment-Receipt (sequenced, seq #)
        │
        ├─ batch worker commits the batch, anchors the root
        ▼
GET /x402/payments/:id/receipt
  └─▶ Merkle inclusion proof ──▶ verify against on-chain commitment
```

## Related

* [Sequencer x402 reference](/stateset-sequencer/stateset-sequencer-x402) — every field and endpoint
* [Set L2 verification example](/set/stateset-set-l2-verification-example) — proving inclusion
* [Sequencer architecture](/stateset-sequencer-architecture) — batching and anchoring
