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

# TypeScript Client Manual Flow

> Understand the x402 v2 client handshake in TypeScript, including requirement validation, signing responsibilities and settlement responses.

## Manual x402 client flow (TypeScript)

This page walks through the **x402 v2 client handshake** in TypeScript without
`@x402/fetch` or `@x402/axios` wrappers. It is an advanced protocol reference,
not a complete copy-and-run client: signing, policy validation and recovery must
be implemented separately. The signing snippets deliberately contain placeholders.

For production you'll usually use the [Fetch](/x402/clients/typescript/fetch) or [Axios](/x402/clients/typescript/axios) quickstarts; this tutorial is for agents or environments that need a minimal, dependency-light implementation or want to understand the protocol step by step.

***

## 1. Request the resource

Send a normal GET (or other method) to the protected URL. No special headers yet.

```ts theme={null}
const url = "http://localhost:4021/weather";
const response = await fetch(url, {
  method: "GET",
  redirect: "error",
  signal: AbortSignal.timeout(10_000),
});
```

***

## 2. Handle 402 and decode PAYMENT-REQUIRED

If the server requires payment, it returns **402 Payment Required** and puts the payment options in the **PAYMENT-REQUIRED** header (base64-encoded JSON).

This walkthrough stops for any other status. In an application, handle that
original response without continuing to payment creation. Decoding JSON or
casting a TypeScript type does not validate untrusted payment requirements.

```ts theme={null}
if (response.status !== 402) {
  throw new Error(`Not a payment challenge (${response.status}); stop this walkthrough`);
}

const paymentRequiredB64 = response.headers.get("PAYMENT-REQUIRED");
if (!paymentRequiredB64) throw new Error("402 without PAYMENT-REQUIRED header");

const paymentRequiredJson = Buffer.from(paymentRequiredB64, "base64").toString("utf-8");
const paymentRequired = JSON.parse(paymentRequiredJson) as {
  x402Version: number;
  error?: string;
  resource: { url: string; description?: string; mimeType?: string };
  accepts: Array<{
    scheme: string;
    network: string;
    amount: string;
    asset: string;
    payTo: string;
    maxTimeoutSeconds: number;
    extra?: Record<string, unknown>;
  }>;
  extensions?: Record<string, unknown>;
};
```

Exact field names and shapes are in the [x402 Reference](/x402/reference). You must use **x402Version: 2** and the **accepts** array.

***

## 3. Choose an accepted option

Before selecting, validate the message at runtime: version 2, expected resource
and recipient, supported exact scheme/network/asset, positive atomic-unit amount
within your spending cap, supported transfer method/payment flow and a bounded
timeout. Network-prefix matching alone is not spending authorization. The example
below assumes that validation and policy decision have already produced
`approvedNetwork`, `approvedAsset`, `approvedRecipient` and `maxAmountAtomic`.
Keep caps in integer atomic units, never floating-point token amounts.

```ts theme={null}
// EVM path:
const accepted = paymentRequired.accepts.find((a) =>
  a.scheme === "exact" && a.network === approvedNetwork &&
  a.asset === approvedAsset && a.payTo === approvedRecipient &&
  /^[1-9][0-9]*$/.test(a.amount) && BigInt(a.amount) <= maxAmountAtomic
);
if (!accepted) throw new Error("No payment option approved by local policy");
```

***

## 4. Build the payment payload

For the EIP-3009 variant of **exact EVM**, produce the authorization and sign its
correct EIP-712 domain/types. Do not use this shape for other transfer methods.
The outer v2 envelope has `accepted`, with scheme/network nested inside it;
it does not use the old top-level scheme/network layout. Preserve the chosen
requirement, resource and advertised extensions. See [x402 Reference](/x402/reference).

Example shape (simplified; real code must use correct domain, types, and signing):

```ts theme={null}
import { randomBytes } from "node:crypto";

// You need: payer private key, accepted requirement, token contract (asset), validAfter/validBefore, nonce
const payload = {
  x402Version: 2,
  resource: paymentRequired.resource,
  accepted,
  payload: {
    signature: "0x...", // EIP-712 signature from signTypedData
    authorization: {
      from: payerAddress, // Replace with your wallet address
      to: accepted.payTo,
      value: accepted.amount,
      validAfter: "0",
      validBefore: String(Math.floor(Date.now() / 1000) + Math.min(60, accepted.maxTimeoutSeconds)),
      nonce: "0x" + randomBytes(32).toString("hex"),
    },
  },
  extensions: paymentRequired.extensions ?? {},
};
const paymentSignatureB64 = Buffer.from(JSON.stringify(payload)).toString("base64");
```

#### Solana (exact scheme)

For an approved **Solana exact** option, construct a partially signed transaction
with a compatible SDK. Compute-budget limits and optional instruction support
depend on the SDK and facilitator policy; they are not universal constants.
Validate network, mint, amount, recipient, fee payer and the complete instruction
set before signing. Do not add or change transaction instructions after signing.

You need a Solana SDK (e.g. `@solana/web3.js` or `@solana/spl-token`) to build and sign the transaction. Source and destination are **token accounts** (e.g. associated token accounts for the mint); `accepted.payTo` is the recipient **wallet**. Decimals may be in `accepted.extra`. The payment payload is JSON with the transaction in `payload.transaction` as base64:

```ts theme={null}
// Alternative to the EVM block: use an exact Solana requirement approved
// by the same explicit resource/network/asset/recipient/budget policy.

// 4. Build Solana transaction (pseudocode — use @solana/web3.js / @solana/spl-token in practice)
// - Configure compute-budget instructions within the facilitator's current policy
// - Add createTransferCheckedInstruction: amount=accepted.amount, mint=accepted.asset,
//   decimals from token metadata or accepted.extra, source=payerTokenAccount, destination=payeeTokenAccount
// - Finalize all instructions, then partially sign with the payer keypair
// - Serialize transaction to buffer, then base64

// const transactionBuffer = serializedSignedTx;  // from your Solana library
const transactionB64 = Buffer.from(transactionBuffer).toString("base64");

const payload = {
  x402Version: 2,
  resource: paymentRequired.resource,
  accepted,
  payload: {
    transaction: transactionB64,
  },
  extensions: paymentRequired.extensions ?? {},
};
const paymentSignatureB64 = Buffer.from(JSON.stringify(payload)).toString("base64");
```

Exact instruction formats, token-account derivation, and limits are in the [x402 Reference (§6.2 Solana)](/x402/reference).

***

## 5. Resend the request with PAYMENT-SIGNATURE

Send the **same** request again (same URL and method), this time adding the **PAYMENT-SIGNATURE** header.

Do this only after producing a real signature under your spending policy; the
placeholders above are not payable. Preserve any original request body and
required application headers. Do not forward payment authorization to redirects.

```ts theme={null}
const retryResponse = await fetch(url, {
  method: "GET",
  redirect: "error",
  signal: AbortSignal.timeout(10_000),
  headers: {
    "PAYMENT-SIGNATURE": paymentSignatureB64,
  },
});
```

***

## 6. Parse the response and PAYMENT-RESPONSE

Inspect the HTTP status and any **PAYMENT-RESPONSE** settlement details separately.
An HTTP success alone is not proof of settlement. Decode an available settlement
header even on an error response, and validate its fields before using it.

```ts theme={null}
const paymentResponseB64 = retryResponse.headers.get("PAYMENT-RESPONSE");
if (paymentResponseB64) {
  const paymentResponse = JSON.parse(
    Buffer.from(paymentResponseB64, "base64").toString("utf-8")
  ) as { success: boolean; transaction: string; network: string; payer?: string; errorReason?: string };
  console.log("Reported settlement (validate before use):", paymentResponse);
  if (paymentResponse.success !== true) {
    throw new Error("Settlement not confirmed; retain the response and reconcile before paying again");
  }
} else {
  console.log("Settlement details unavailable; do not infer settlement from HTTP status");
}
if (!retryResponse.ok) throw new Error(`Request failed: ${retryResponse.status}; reconcile before paying again`);
console.log(await retryResponse.json());
```

**Decoded PAYMENT-RESPONSE examples**

Illustrative success (EVM); transaction identifiers below are not observed receipts:

```json theme={null}
{
  "success": true,
  "transaction": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "network": "eip155:84532",
  "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66"
}
```

Illustrative success (Solana devnet / SVM):

```json theme={null}
{
  "success": true,
  "transaction": "AXGcLa7sqSjt7pXV4mpVRP5a77tVjokZbgx8gkQ16X8Wgg3vDkWKMh9BrPTr1f2KrDuf9nSX7FrZEAQTkJ3y5UN",
  "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
  "payer": "6KPYDyuRnpuKcm1TerUmwLd2BcaihvhF4Ccrr8beruu2"
}
```

Illustrative decoded **PAYMENT-RESPONSE** for a known settlement failure, with no
transaction broadcast (not a PAYMENT-REQUIRED challenge):

```json theme={null}
{
  "success": false,
  "errorReason": "insufficient_funds",
  "transaction": "",
  "network": "eip155:84532"
}
```

A timeout, missing header or `settlement_pending` is not a confirmed failure.
Retain the original request/payment identifiers and follow
[capacity and recovery guidance](/x402/facilitators/capacity-and-limits).
Do not automatically create a fresh payment or infer finality from decoded JSON.

***

## Summary

| Step | Action |
| - | - |
| 1 | GET (or other method) the resource URL |
| 2 | If status is 402, decode **PAYMENT-REQUIRED** (base64 JSON) |
| 3 | Choose one entry from **accepts** |
| 4 | Build payment payload (EIP-712 sign for EVM exact scheme; or Solana tx) and base64-encode it |
| 5 | Resend the same request with **PAYMENT-SIGNATURE** header |
| 6 | On 200, use response body and optionally decode **PAYMENT-RESPONSE** |

For exact field names, types, and facilitator usage, see the [x402 Reference](/x402/reference). For a ready-made client, use the [Fetch](/x402/clients/typescript/fetch) or [Axios](/x402/clients/typescript/axios) quickstarts.

## Need help?

<Card title="Join our Community" icon="discord" href="https://discord.gg/eWJRwMpebQ">
  Have questions or want to connect with other developers? Join our Discord server.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.