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

# x402 Payments

> The x402 HTTP 402 payment protocol — how resource servers get paid and how agents pay, using real EIP-3009 signatures.

<Note>
  This page covers **concepts and end-to-end guides** for `@xentfi/x402-facilitator-sdk`. For the client method/type reference, see [x402 Reference](/agent/x402-reference) in the Agent tab.
</Note>

## Overview

[x402](https://github.com/coinbase/x402) is an open convention for monetizing HTTP resources using the (long-reserved, rarely-used) **`402 Payment Required`** status code. A resource server responds `402` with a machine-readable list of accepted payment requirements; the client — often an AI agent — signs a payment authorization and retries the request with an `X-PAYMENT` header attached. It's built for agent-to-agent and agent-to-API payments where there's no human clicking "buy."

XentFi's facilitator implements the **`eip3009`** scheme: payments are [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `TransferWithAuthorization` signatures — gasless, off-chain-signed authorizations that a relayer (your XentFi WAAS wallet) submits on-chain during settlement.

```bash theme={null}
npm install @xentfi/x402-facilitator-sdk
```

```mermaid theme={null}
sequenceDiagram
    participant Payer as Paying Agent
    participant Server as Resource Server (you)
    participant Facilitator as XentFi x402 Facilitator
    participant Chain as Blockchain

    Payer->>Server: GET /premium-report
    Server-->>Payer: 402 Payment Required<br/>{ accepts: [PaymentRequirement] }
    Payer->>Payer: Sign EIP-3009 authorization<br/>(signEip3009Authorization)
    Payer->>Server: GET /premium-report<br/>X-PAYMENT: base64(payload)
    Server->>Facilitator: POST /verify
    Facilitator-->>Server: { isValid: true }
    Server->>Facilitator: POST /settle
    Facilitator->>Chain: Submit TransferWithAuthorization
    Chain-->>Facilitator: txHash
    Facilitator-->>Server: { success: true, txHash, network }
    Server-->>Payer: 200 OK + report + txHash
```

<CardGroup cols={2}>
  <Card title="💰 Resource server" icon="server">
    You're getting paid. Use `X402FacilitatorClient` to `verify()` an incoming payment and `settle()` it on-chain — your WAAS wallet relays the transaction and receives the funds.
  </Card>

  <Card title="🤖 Payer" icon="bot">
    You're paying. Use `signEip3009Authorization()` to build a real, offline EIP-712 signature from any [viem](https://viem.sh) `LocalAccount`, then attach it as the `X-PAYMENT` header.
  </Card>
</CardGroup>

<Note>
  The `apiKey`/`orgId` used here is your **organization's WAAS credential**, not an agent API key — see [Authentication](/agent/authentication). A pure payer needs no XentFi credential at all, only a viem-compatible signing account.
</Note>

## Getting paid (resource server)

### Define what you accept

```ts theme={null}
import type { PaymentRequirement } from "@xentfi/x402-facilitator-sdk";

const requirement: PaymentRequirement = {
  scheme: "eip3009",
  network: "base-mainnet",
  blockchainId: "2fad7eb1-4405-4400-af4f-2b37401a6bd6",
  assetId: "b3d641bb-f28f-4d1f-b89c-a504ad51f453",
  maxAmountRequired: "1.00",
  payTo: "0xYourWalletAddress",
  resource: "GET /premium-report",
  maxTimeoutSeconds: 3600,
};
```

Use `facilitator.getSupported()` to find valid `blockchainId`/`assetId` values — it requires no authentication.

### Challenge, verify, settle

```ts theme={null}
import Fastify from "fastify";
import { X402FacilitatorClient } from "@xentfi/x402-facilitator-sdk";
import type { PaymentPayload, PaymentRequirement } from "@xentfi/x402-facilitator-sdk";

const facilitator = new X402FacilitatorClient({
  apiKey: process.env.WAAS_API_KEY!,
  orgId: process.env.WAAS_ORG_ID!,
});

const app = Fastify();

app.get("/premium-report", async (req, reply) => {
  const proofHeader = req.headers["x-payment"];

  if (!proofHeader) {
    // No payment attached yet — issue the 402 challenge.
    return reply.status(402).send({ x402Version: 1, accepts: [requirement] });
  }

  const paymentPayload: PaymentPayload = JSON.parse(
    Buffer.from(proofHeader as string, "base64").toString("utf8"),
  );

  try {
    // Throws on invalid signature, expired authorization, wrong amount, etc.
    await facilitator.verify(paymentPayload, requirement);

    // Submits the TransferWithAuthorization on-chain via your WAAS relayer wallet.
    const settlement = await facilitator.settle(paymentPayload, requirement);

    return { report: "... the actual paid content ...", txHash: settlement.txHash };
  } catch (error) {
    return reply.status(402).send({
      x402Version: 1,
      accepts: [requirement],
      error: (error as Error).message,
    });
  }
});

app.listen({ port: 3000 });
```

<Steps>
  <Step title="No X-PAYMENT header">
    Respond `402` with `{ x402Version: 1, accepts: [requirement] }` — the machine-readable challenge the payer's agent parses.
  </Step>

  <Step title="X-PAYMENT header present">
    Base64-decode it into a `PaymentPayload` and call `facilitator.verify(payload, requirement)` — checks the EIP-712 signature, amount, recipient, and validity window. No funds move yet.
  </Step>

  <Step title="Verified — settle it">
    Call `facilitator.settle(payload, requirement)`. Your WAAS wallet relays the `TransferWithAuthorization` on-chain and pays the gas; funds land at `payTo`.
  </Step>

  <Step title="Serve the content">
    Once `settle()` resolves, return the actual paid resource along with the `txHash`.
  </Step>
</Steps>

<Warning>
  Both `verify()` and `settle()` **throw** on failure — catch the error and fold its message into your own `402` response's `error` field, as shown above. See [Error Handling](/agent/errors) for exact error classes and status mapping.
</Warning>

## Paying as an agent

This is the "you're making a payment" side — typically an agent that needs to pay for an API call and keep going without a human in the loop.

```ts theme={null}
import { privateKeyToAccount } from "viem/accounts";
import { signEip3009Authorization } from "@xentfi/x402-facilitator-sdk";
import type { Eip3009TokenDomain, X402Challenge } from "@xentfi/x402-facilitator-sdk";

const account = privateKeyToAccount(process.env.PAYER_PRIVATE_KEY! as `0x${string}`);

const domain: Eip3009TokenDomain = {
  chainId: 8453,
  tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  tokenName: "USD Coin",
  tokenVersion: "2",
  tokenDecimals: 6,
};

async function main() {
  const url = "https://merchant.example.com/premium-report";

  // 1. First request — expect a 402 challenge.
  const firstResponse = await fetch(url);
  if (firstResponse.status !== 402) return console.log(await firstResponse.json());

  const challenge = (await firstResponse.json()) as X402Challenge;
  const requirement = challenge.accepts.find((r) => r.scheme === "eip3009");
  if (!requirement) throw new Error("Server doesn't support the 'eip3009' scheme");

  // 2. Sign a real EIP-3009 authorization for the required amount.
  const paymentPayload = await signEip3009Authorization({
    account,
    to: requirement.payTo,
    amount: requirement.maxAmountRequired,
    domain,
  });

  // 3. Retry with the signed payment attached.
  const header = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
  const secondResponse = await fetch(url, { headers: { "X-PAYMENT": header } });
  console.log(secondResponse.status, await secondResponse.json());
}

main();
```

<Steps>
  <Step title="Probe the endpoint">
    A `402` response means payment is required; the body is an `X402Challenge` listing accepted `PaymentRequirement`s.
  </Step>

  <Step title="Pick a requirement your agent can pay">
    Filter `challenge.accepts` for a `scheme`/`network`/`assetId` you support — currently `eip3009`.
  </Step>

  <Step title="Sign, don't call the facilitator directly">
    `signEip3009Authorization()` is pure local cryptography — it never touches the network. Your agent's private key never leaves the client.
  </Step>

  <Step title="Attach and retry">
    Base64-encode the signed payload and send it as `X-PAYMENT`. The resource server calls `verify()`/`settle()` on your behalf — your agent doesn't need a XentFi credential at all to *pay*.
  </Step>
</Steps>

## EIP-3009 signing reference

```ts theme={null}
function signEip3009Authorization(
  params: SignExactAuthorizationParams
): Promise<Eip3009SchemePayload>;
```

| Parameter         | Type                 | Required | Description                                                                                              |
| ----------------- | -------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| `account`         | `LocalAccount`       | ✅        | Any [viem](https://viem.sh) local account implementing `signTypedData`.                                  |
| `to`              | `Address`            | ✅        | Recipient address (the merchant's `payTo`).                                                              |
| `amount`          | `string`             | ✅        | Human-readable units, e.g. `"2.50"` — converted to base units via `domain.tokenDecimals`.                |
| `domain`          | `Eip3009TokenDomain` | ✅        | The token's real EIP-712 domain — see below.                                                             |
| `validitySeconds` | `number`             | optional | Validity window in seconds. Default `300` (5 minutes).                                                   |
| `nonce`           | `Hex`                | optional | Supply your own bytes32 nonce for deterministic/idempotent authorizations; otherwise randomly generated. |

### Getting the token domain right

`Eip3009TokenDomain` must match the deployed contract exactly, or both the facilitator and the token contract will reject the signature:

| Field           | Description                                                                                   |
| --------------- | --------------------------------------------------------------------------------------------- |
| `chainId`       | Numeric chain ID (e.g. `8453` for Base mainnet).                                              |
| `tokenAddress`  | The ERC-20 contract address.                                                                  |
| `tokenName`     | The contract's EIP-712 domain `name`.                                                         |
| `tokenVersion`  | The contract's EIP-712 domain `version` (commonly `"1"` or `"2"` — check the specific token). |
| `tokenDecimals` | Used to convert `amount` into base units.                                                     |

Pull these from the merchant's `PaymentRequirement`/`getSupported()` response rather than hardcoding — different tokens on different chains use different `version` strings, and getting it wrong produces a signature the contract silently rejects.

<Tip>
  A deterministic `nonce` (derived from your own request/order ID) lets you safely retry signing the *same* logical payment without risking a double-spend — the contract rejects a reused nonce for the same signer.
</Tip>

### Security notes

* `validBefore` is set from `validitySeconds` — keep this short. A long-lived signed authorization is a bearer instrument until it expires or is consumed.
* `validAfter` is always `0` (immediately valid) in the current implementation.
* The signature never leaves the caller's process during signing — only the final `Eip3009SchemePayload` (which includes the signature, not the private key) is transmitted anywhere.

`buildEip3009Domain(token: Eip3009TokenDomain)` builds the matching EIP-712 `domain` object if you need to construct or verify a signature manually.

## Related

<CardGroup cols={3}>
  <Card title="x402 Reference" icon="code" href="/agent/x402-reference">
    `X402FacilitatorClient` methods and full type surface.
  </Card>

  <Card title="Error Handling" icon="alert-triangle" href="/agent/errors">
    Error classes, retries, and HTTP status mapping.
  </Card>

  <Card title="Agent Tools guide" icon="plug" href="/essentials/agent">
    The other way an agent can transact — manage its own wallet.
  </Card>
</CardGroup>
