---
title: "x402決済 2026 v2の実装と精算検証"
locale: ja
category: tutorial
category_name: "チュートリアル"
translation_status: reviewed
license: cc_by
author: "Injoys 編集部"
source_url: https://injoys.com/ja/articles/x402-payments-2026-v2-base-usdc-settlement-guide
published_at: 2026-10-28T19:20:55+09:00
---

# x402決済 2026 v2の実装と精算検証

> x402 v2でAPIに少額決済を組み込むNode.jsの実習です。Base Sepoliaで決済条件を選択し、署名を再要求して精算を検証し、再送とメインネットへの移行条件を確認してください。

## Key Points

- 受取先アドレスとテスト決済用ウォレットを用意し、公式のx402 SDKをインストールしてください。
- Base Sepolia用の決済ミドルウェアを接続し、決済なしのリクエストに対する402レスポンスを確認してください。
- クライアント側で許可したチェーン・トークン・金額・受取人に合う条件だけを選び、署名してください。
- 精算レスポンスのトランザクションハッシュでUSDC Transferイベントを照会してください。
- 失敗時のブロックと重複リクエストの復旧をテストした後、メインネットの設定を検討してください。

x402決済は、APIの402レスポンスに決済条件を含めて実装します。Node.jsとテスト用ウォレットを用意してください。まず、Base Sepoliaでv2の署名と精算を検証します。有料レスポンスは、精算が成功した後に返すように設定してください。

金額・チェーン情報は、出典としてリンクしたx402・Base・Circleの公式ドキュメントに基づいています。

## 実習の準備とインストール

APIを変更できる開発環境とテスト用ウォレットを用意してください。サンプルはNode.js 22とx402 SDK 2.28.0を基準に構成しています。インストール構成は[公式販売者ガイド](https://docs.x402.org/getting-started/quickstart-for-sellers)に従います。

1. 決済を受け取るEVMウォレットのアドレスを用意してください。
2. 決済用のテストウォレットを別途用意してください。
3. 決済用ウォレットにBase Sepoliaのテスト用USDCを用意してください。
4. 以下のコマンドで実習用プロジェクトを作成してください。
5. 以降のTypeScriptファイルを同じフォルダーに保存してください。

```bash
mkdir x402-demo
cd x402-demo
npm init -y
npm pkg set type=module
npm install --save-exact express@5.3.0 viem@2.57.12 @x402/core@2.28.0 @x402/express@2.28.0 @x402/evm@2.28.0 @x402/fetch@2.28.0
npm install --save-dev --save-exact typescript tsx @types/node @types/express
```

受取側のサーバーに、受取ウォレットの秘密鍵は必要ありません。決済用の秘密鍵は、クライアントプロセスでのみ読み込む構成にしてください。ウォレットの署名方式は[公式購入者ガイド](https://docs.x402.org/getting-started/quickstart-for-buyers)に基づいています。

このサンプルの検証範囲は、型チェックと模擬的な決済フローです。実際にテストネットでの送金まで完了したサンプルではありません。以下の実行手順で、オンチェーンの結果を直接確認してください。

## x402 v1とv2の比較

v2を実装する際は、ヘッダーと本文のフィールドを両方そろえる必要があります。ヘッダー名だけを変更しても、データ構造が一致しません。次の表は[公式移行ガイド](https://docs.x402.org/guides/migration-v1-to-v2)に基づいています。

| 区分 | v1 | v2 |
|---|---|---|
| プロトコルバージョン | `x402Version: 1` | `x402Version: 2` |
| 初回の決済条件 | 402レスポンスのJSON本文 | `PAYMENT-REQUIRED`レスポンスヘッダー |
| 署名済み決済データ | `X-PAYMENT`リクエストヘッダー | `PAYMENT-SIGNATURE`リクエストヘッダー |
| 精算結果 | `X-PAYMENT-RESPONSE`レスポンスヘッダー | `PAYMENT-RESPONSE`レスポンスヘッダー |
| Baseメインネット識別子 | `base` | `eip155:8453` |
| Base Sepolia識別子 | `base-sepolia` | `eip155:84532` |
| 決済条件の金額フィールド | `maxAmountRequired` | `amount` |
| リソースの説明 | 決済条件内の`resource`など | 最上位の`resource`オブジェクト |
| 決済ペイロードの条件 | 最上位の`scheme`、`network`など | 選択した条件を格納する`accepted`オブジェクト |
| TypeScriptパッケージの例 | `x402-express` | `@x402/express` |

v1で決済条件を渡す場所は、[HTTP v1仕様](https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md)に明記されています。v2で標準となる受け渡し場所は、`PAYMENT-REQUIRED`ヘッダーです。実装によっては本文も返す場合がありますが、ヘッダーを基準に処理してください。

[HTTP v2仕様](https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md)では、ステータスコードを次のように説明しています。

> サーバーは、HTTP 402「Payment Required」ステータスコードを使って、決済が必要であることを示します。
>
> 出典：x402 HTTP v2仕様

v2の決済ヘッダーは、JSONをBase64でエンコードした値です。Base64は暗号化方式ではありません。外部向けサービスではHTTPSを使用してください。

## HTTP 402リクエストと決済の順序

この実習では、EVMの`exact`方式の基本的な承認フローを使用します。`exact`は、指定された金額を決済する方式です。順序は[x402 v2の基本仕様](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md)に従います。

1. クライアントが決済情報なしで有料APIをリクエストします。
2. サーバーがHTTP 402と`PAYMENT-REQUIRED`を返します。
3. クライアントが`accepts`から許可された条件を選択します。
4. SDKが、選択した条件に合う決済承認に署名します。
5. クライアントが`PAYMENT-SIGNATURE`を付けて再リクエストします。
6. サーバーがfacilitatorの`/verify`で承認を検証します。
7. 検証を通過すると、サーバーがリソースの処理を実行します。
8. サーバーが`/settle`で精算をリクエストします。
9. 精算が成功した後、有料の本文と`PAYMENT-RESPONSE`を返します。

この順序は、すべての決済方式に共通する固定の順序ではありません。v2には、精算のタイミングが異なるフローも定義されています。以下のコードでは、基本的な`authorization`フローのみを許可します。

### Resource serverとfacilitatorの役割

Resource serverは、価格とコンテンツへのアクセス条件を決定します。Facilitatorは、決済の検証と精算を担当します。役割の区分は[公式facilitatorドキュメント](https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md)に基づいています。

| 構成要素 | 担当する処理 | 成功と見なしてはいけない状態 |
|---|---|---|
| クライアント | 条件の選択と決済承認への署名 | 署名の生成だけが完了した状態 |
| Resource server | 価格の宣言と有料レスポンスの制御 | クライアントが成功したと主張している状態 |
| Facilitator `/verify` | 署名と決済条件の有効性確認 | `isValid: true`を受け取っただけの状態 |
| Facilitator `/settle` | この実習ではオンチェーン決済の実行と結果の返却 | 送信したものの結果を確認できていない状態 |

`/verify`は、オンチェーンの状態を変更しない検査です。そのため、検証の成功は入金の完了を意味しません。検証後も、残高や承認状態が変わる可能性があります。

検証や精算に失敗した場合は、有料の本文をブロックしてください。成功を確認できなかったときにブロックする処理を、fail-closedと呼びます。この動作は[公式Express実装](https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts)でも確認できます。

## Baseメインネットとテストネットの比較

初回の実行では、Base Sepoliaだけを使用してください。Circleは、テストネットのUSDCに金銭的価値がないと明記しています。アドレスは[CircleのUSDCコントラクト一覧](https://developers.circle.com/stablecoins/usdc-contract-addresses)に基づいています。

| 項目 | Baseメインネット | Base Sepoliaテストネット |
|---|---|---|
| 用途 | 実際の資産による決済 | 実装と障害処理のテスト |
| Chain ID | `8453` | `84532` |
| x402 v2ネットワーク | `eip155:8453` | `eip155:84532` |
| USDCコントラクト | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |
| 公開RPC | `https://mainnet.base.org` | `https://sepolia.base.org` |
| トークンの金銭的価値 | 実際に金銭的価値のある資産 | 金銭的価値なし |

Chain IDとRPCは、[Baseネットワークドキュメント](https://docs.base.org/get-started/connect-to-base)で確認してください。同じウォレットアドレスでも、チェーンが異なれば残高は別です。テストネットでの成功を、実際の売上として記録してはいけません。

テスト用USDCは、[Circle Testnet Faucet](https://faucet.circle.com/)でリクエストできます。ネットワークにはBase Sepoliaを選択してください。受取アドレスには、決済用テストウォレットのアドレスを入力します。

## Node.jsによる有料APIの実装

有料ルートの前に`paymentMiddleware`を組み込んでください。以下のサンプルは、呼び出し1回あたり**0.001 USDC**を要求します。金額は、公式販売者ガイドの価格例を使用しています。（確認値：呼び出し1回あたり0.001 USDC・出典：docs.x402.org・2026-10-28確認）

### サーバーファイル

次のコードを`server.ts`として保存してください。サーバーは、ローカルインターフェースでのみリクエストを受け付けます。受取アドレスは、環境変数`PAY_TO`から読み込みます。

```typescript
import express from "express";
import { getAddress, zeroAddress } from "viem";
import { paymentMiddleware } from "@x402/express";
import { HTTPFacilitatorClient, x402ResourceServer } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";

const payTo = getAddress(process.env.PAY_TO ?? "");
if (payTo === zeroAddress) throw new Error("Invalid payTo");
const network = "eip155:84532";
const resourceServer = new x402ResourceServer(
  new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" }),
).register(network, new ExactEvmScheme());

const app = express();
app.use((_req, res, next) => {
  res.setHeader("Cache-Control", "private, no-store");
  next();
});
app.use(paymentMiddleware({
  "GET /paid-data": {
    accepts: [{ scheme: "exact", price: "$0.001", network, payTo }],
    description: "Sample paid JSON",
    mimeType: "application/json",
  },
}, resourceServer));
app.get("/paid-data", (_req, res) => {
  res.json({ content: "Paid sample content" });
});
app.listen(4021, "127.0.0.1");
```

`https://x402.org/facilitator`は、公式ガイドに掲載されているテスト用のアドレスです。このサンプルでは、Base Sepoliaでの精算に使用します。本番用facilitatorの選択については、[販売者ガイドのメインネット手順](https://docs.x402.org/getting-started/quickstart-for-sellers#running-on-mainnet)を確認してください。

### 価格計算の例

サンプルの価格は、USDCの最小単位で`1000`です。USDCの小数点以下の桁数は6桁です。SDKによる価格変換は、[EVMサーバー実装](https://github.com/x402-foundation/x402/blob/main/typescript/packages/mechanisms/evm/src/exact/server/scheme.ts)に基づいています。

```text
0.001 USDC × 10^6 = 1000 최소 단위
```

| 確認する値 | このサンプルでの期待値 |
|---|---|
| サーバー設定の`price` | `"$0.001"` |
| 402決済条件の`amount` | `"1000"` |
| 決済条件の`asset` | Base SepoliaのUSDCコントラクト |
| 決済条件の`payTo` | 設定した受取アドレス |
| 決済条件の`network` | `eip155:84532` |

`price`はSDKの設定値です。実際のv2決済条件では、`amount`が渡されます。整数の金額は、文字列または`bigint`として比較してください。

### サーバーの起動と402の確認

1つ目のBashターミナルでサーバーを起動してください。入力する値は、秘密鍵ではなく公開の受取アドレスです。実行中は、このターミナルを開いたままにします。

```bash
read -r -p '수취 지갑 주소: ' PAY_TO
export PAY_TO
npx tsx server.ts
```

2つ目のターミナルから、決済なしのリクエストを送信してください。以下のコマンドは、決済に署名しません。HTTPステータスと、デコードした決済条件を出力します。

```bash
node --input-type=module <<'JS'
const response = await fetch("http://127.0.0.1:4021/paid-data");
const header = response.headers.get("PAYMENT-REQUIRED");
console.log(response.status);
console.log(header && JSON.parse(
  Buffer.from(header, "base64").toString("utf8")
));
JS
```

正常な決済なしのレスポンスは、HTTP 402です。出力された条件を、上の表と照合してください。有料コンテンツがそのまま返される場合は、ルートの保護設定から修正します。

## 決済条件の選択とクライアントの実装

許可した条件にのみ署名するように、クライアントを制限してください。このサンプルでは、Base SepoliaのUSDCによる指定価格の決済だけを許可します。決済オプションのフィルターには、[公式x402Client実装](https://github.com/x402-foundation/x402/blob/main/typescript/packages/core/src/client/x402Client.ts)の`registerPolicy`を使用します。

1. 以下のコードを`client.ts`として保存してください。
2. 次の節の`check-transfer.ts`も保存してください。
3. 2つのファイルを用意してから、実行手順に進んでください。

```typescript
import { writeFile } from "node:fs/promises";
import { getAddress, zeroAddress, type Hex, type Hash } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { decodePaymentResponseHeader } from "@x402/core/http";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { checkTransfer } from "./check-transfer.js";

const key = process.env.EVM_PRIVATE_KEY ?? "";
if (!/^0x[0-9a-fA-F]{64}$/.test(key)) throw new Error("Invalid test key");
const signer = privateKeyToAccount(key as Hex);
const payTo = getAddress(process.env.PAY_TO ?? "");
if (payTo === zeroAddress) throw new Error("Invalid payTo");
const network = "eip155:84532";
const token = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
const client = new x402Client().register(network, new ExactEvmScheme(signer));
client.registerPolicy((version, options) => version === 2
  ? options.filter(r =>
      r.scheme === "exact" && r.network === network &&
      r.asset.toLowerCase() === token.toLowerCase() &&
      r.payTo.toLowerCase() === payTo.toLowerCase() && r.amount === "1000" &&
      (!r.extra?.assetTransferMethod || r.extra.assetTransferMethod === "eip3009") &&
      (!r.extra?.paymentFlow || r.extra.paymentFlow === "authorization"))
  : []);

const paidFetch = wrapFetchWithPayment(fetch, client);
const response = await paidFetch("http://127.0.0.1:4021/paid-data", {
  redirect: "error",
});
const raw = response.headers.get("PAYMENT-RESPONSE");
if (!raw) throw new Error(`No settlement receipt: HTTP ${response.status}`);
const settlement = decodePaymentResponseHeader(raw);
await writeFile("settlement.json", JSON.stringify(settlement, null, 2));
if (!response.ok || !settlement.success || settlement.network !== network) {
  throw new Error("Payment unresolved; inspect settlement.json before retrying");
}
const evidence = await checkTransfer(
  network, settlement.transaction as Hash, signer.address, payTo, 1000n,
);
await writeFile("transfer.json", JSON.stringify(evidence, null, 2));
console.log(await response.text());
```

`wrapFetchWithPayment`が、402の処理と署名付きの再リクエストを担当します。使い方は[公式fetchサンプル](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch)に従います。許可された決済条件がなければ、決済の生成を中止します。

AIエージェントでも、このクライアントを使用できます。ただし、呼び出し1回あたりの価格制限は、累積支出の制限ではありません。エージェントによる繰り返し呼び出しの予算は、別途管理してください。

## USDC Transferイベントの検証

精算結果のトランザクションハッシュを使って、USDCの送金ログを確認してください。トランザクションの成功状態だけでは、受取人まで確認できません。`Transfer`のフィールドは、[ERC-20標準](https://eips.ethereum.org/EIPS/eip-20)に基づいています。

検証には、次の項目が必要です。

| 検証項目 | 比較対象 |
|---|---|
| RPCのChain ID | サーバーが許可したチェーン |
| トランザクションレシートの`status` | 成功状態 |
| イベントを発生させたアドレス | そのチェーンの公式USDCコントラクト |
| `Transfer.from` | 確認済みの支払者 |
| `Transfer.to` | サーバーが指定した受取アドレス |
| `Transfer.value` | サーバーが要求した最小単位での金額 |

以下のコードを`check-transfer.ts`として保存してください。照会には、viemの[getTransactionReceipt](https://viem.sh/docs/actions/public/getTransactionReceipt)を使用します。イベントの解釈は、[parseEventLogs](https://viem.sh/docs/contract/parseEventLogs)に基づいています。

```typescript
import {
  createPublicClient, http, getAddress, parseEventLogs,
  erc20Abi, type Address, type Hash,
} from "viem";
import { base, baseSepolia } from "viem/chains";

const configs = {
  "eip155:84532": {
    chain: baseSepolia,
    rpc: "https://sepolia.base.org",
    token: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
  },
  "eip155:8453": {
    chain: base,
    rpc: "https://mainnet.base.org",
    token: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  },
} as const;

export async function checkTransfer(
  network: keyof typeof configs, hash: Hash,
  payer: Address, payTo: Address, amount: bigint,
) {
  const cfg = configs[network];
  const rpc = createPublicClient({ chain: cfg.chain, transport: http(cfg.rpc) });
  if (await rpc.getChainId() !== cfg.chain.id) throw new Error("Wrong chain");
  const receipt = await rpc.getTransactionReceipt({ hash });
  if (receipt.status !== "success") throw new Error("Transaction failed");
  const logs = parseEventLogs({
    abi: erc20Abi,
    eventName: "Transfer",
    logs: receipt.logs.filter(
      log => getAddress(log.address) === getAddress(cfg.token),
    ),
    strict: true,
  });
  const transfer = logs.find(log =>
    getAddress(log.args.from) === getAddress(payer) &&
    getAddress(log.args.to) === getAddress(payTo) &&
    log.args.value === amount,
  );
  if (!transfer) throw new Error("Matching USDC Transfer not found");
  return {
    network, transaction: hash, logIndex: transfer.logIndex,
    blockHash: receipt.blockHash, blockNumber: receipt.blockNumber.toString(),
  };
}
```

この関数は、そのトランザクションでUSDCが送金された事実を確認します。特定のAPI注文に対応するものかどうかは、別途確認する必要があります。本番サーバーでは、保存済みの注文条件を比較値として使用してください。

クライアントがアップロードした`transfer.json`を、アクセス許可の証拠として信用してはいけません。このサンプルのサーバーは、facilitatorの検証・精算結果を使用します。独立した照会をアクセス許可の条件にする場合は、サーバー側で実行する必要があります。

レシートの照会に失敗しても、それだけで決済の失敗を意味するわけではありません。RPCエラーであれば、保存済みのトランザクションハッシュを再照会してください。追加の承認数とチェーン再編成への対応は、運用ポリシーで定めます。

## テスト決済の実行と結果確認

すべてのファイルを保存してから、テスト用ウォレットで実行してください。型チェックでは、署名や送金は実行されません。以下のコマンドでは、先ほどインストールしたTypeScriptツールを使用します。

1. 次のコマンドで型をチェックしてください。
2. 2つ目のターミナルに、サーバーと同じ受取アドレスを入力してください。
3. テスト決済用ウォレットの秘密鍵を、非表示で入力してください。
4. クライアントを1回実行してください。
5. 保存された精算結果と送金ログを確認してください。

```bash
npx tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext server.ts client.ts check-transfer.ts
read -r -p '같은 수취 지갑 주소: ' PAY_TO
read -r -s -p '테스트 결제 지갑 개인키: ' EVM_PRIVATE_KEY
export PAY_TO EVM_PRIVATE_KEY
npx tsx client.ts
unset EVM_PRIVATE_KEY
```

| 出力物 | 意味 | 確認する内容 |
|---|---|---|
| `settlement.json` | サーバーが返した精算結果 | `success`、`network`、`transaction` |
| `transfer.json` | RPC照会で見つかった送金記録 | チェーン、トランザクションハッシュ、ログ位置、ブロック情報 |
| コンソールの有料本文 | このクライアントでの検証後に出力したデータ | リクエストしたコンテンツかどうかの確認 |

クライアントを新たに実行すると、新しい決済が発生する場合があります。この最小限のサンプルには、注文ごとの重複を防ぐストアはありません。再試行機能は、次の節の条件を満たしてから追加してください。

## 検証失敗の条件別の処理

失敗テストでは、有料の本文が露出しないことを確認してください。HTTPステータスだけをチェックすると、ブロック漏れを見逃す可能性があります。基準となる動作は、[公式Expressミドルウェア](https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts)を参照します。

| テスト条件 | 期待する処理 | 追加確認 |
|---|---|---|
| 決済ヘッダーなし | 402と決済条件を返却 | 有料本文なし |
| 別のトークン・受取人の提示 | クライアントポリシーで拒否 | 署名を生成しない |
| 不正な署名 | 検証失敗としてブロック | 有料ハンドラーを実行しない |
| 残高不足 | 検証または精算の失敗としてブロック | 精算成功として記録しない |
| 精算結果が失敗 | 有料レスポンスをブロック | 準備済みの本文も送信しない |
| facilitatorの応答遅延 | 成功未確認の状態で保留 | 新しい承認の自動生成を禁止 |
| RPCでのレシート照会エラー | トランザクションハッシュを保持して再照会 | 決済失敗と断定しない |

基本的な承認フローでは、精算前にハンドラーが実行されます。精算に失敗しても、外部での処理まで元に戻るわけではありません。このサンプルのように、副作用のない照会から適用してください。

メールの送信や外部での注文作成には、別途設計が必要です。決済と処理の状態を一緒に記録してください。失敗時の再実行範囲を決めることで、処理の重複を減らせます。

## 精算後のレスポンス消失と再送による復旧

決済の完了とコンテンツ配信の完了を、別々の状態として保存してください。決済が完了していても、レスポンスの接続が切れる場合があります。このような状況への対応の根拠となるのは、[Payment-Identifier拡張ドキュメント](https://docs.x402.org/extensions/payment-identifier)です。

同じ処理を再リクエストしても重複処理しない性質を、冪等性と呼びます。`payment-identifier`は、そのための決済識別子を提供します。識別子を追加するだけで、自動的に完成する機能ではありません。

### 重複決済と再送の比較

同じ署名の再利用防止と、注文の重複防止は区別する必要があります。EIP-3009は、使用済みの承認nonceの再利用を防ぎます。この動作は、[ERC-3009仕様](https://eips.ethereum.org/EIPS/eip-3009)に定義されています。

| 状況 | オンチェーン承認の観点 | API運用の観点 |
|---|---|---|
| 同じ承認を再び精算 | 使用済みnonceのチェック対象 | 既存結果の照会が必要 |
| 同じ注文に新しい承認を生成 | 別の承認として処理される可能性 | 重複決済の防止が必要 |
| 精算成功後にレスポンスが消失 | 資産の移動は完了している可能性 | コンテンツの再配信が必要 |
| 精算リクエスト後にタイムアウト | 結果がまだ不明確 | 保留状態と精算確認が必要 |

次の手順は、公式拡張を運用用ストアに接続する設計案です。前述の最小限のコードには含まれていません。本番運用に適用する場合は、永続ストアと同時リクエストの制御を追加してください。

1. サーバー側で`payment-identifier`拡張への対応を宣言してください。
2. クライアント側で論理的なリクエストの識別子を保持してください。
3. サーバー側で識別子を支払者とリクエスト内容に関連付けてください。
4. 処理中の状態をアトミックに記録し、同時実行を防いでください。
5. 精算成功時に、トランザクションの証拠とレスポンス結果を保存してください。
6. 再リクエストの権限と内容が同じであれば、保存済みの結果を返してください。
7. 精算結果が不明確な場合は、既存の承認から確認してください。

決済識別子は、パスワードやアクセストークンではありません。識別子を知っているだけのリクエストに、有料キャッシュを返してはいけません。別のユーザーや異なるリクエスト本文に再利用されていないかもチェックしてください。

運用記録には、チェーン・トランザクションハッシュ・ログ位置を一緒に残してください。この記録を注文に関連付けて、別の注文での再利用をブロックします。`Transfer`の一致だけでは、注文との対応関係までは証明できません。

## 決済実装でよくある間違い

バージョンフィールドと成功を示す情報を混同すると、決済の判定が誤ります。以下の表を、デプロイ時のレビューに活用してください。バージョンの違いは、[x402移行ガイド](https://docs.x402.org/guides/migration-v1-to-v2)に基づいています。

| よくある間違い | 修正の基準 |
|---|---|
| `extra.version: "2"`をx402のバージョンと解釈 | プロトコルバージョンは`x402Version`で確認 |
| v2で`maxAmountRequired`を使用 | 決済条件の金額には`amount`を使用 |
| `X-PAYMENT-REQUIRED`ヘッダーを独自に生成 | v1は本文、v2は`PAYMENT-REQUIRED`を使用 |
| `isValid: true`を入金完了として処理 | `/settle`の結果を別途確認 |
| トランザクションの最上位の`to`をUSDCの受取人として処理 | USDCの`Transfer.to`を確認 |
| 署名者とトランザクション送信者が同じと想定 | 承認の支払者と`Transfer.from`を比較 |
| テストネットでの受取額を売上に合算 | メインネットとテストネットの帳簿を分離 |
| タイムアウトのたびに新しい決済を生成 | 既存の注文と承認状態から復旧 |
| SDKのパッケージバージョンとプロトコルバージョンを同一視 | パッケージバージョンと`x402Version`を別々に管理 |

USDCの署名のドメイン情報にも、バージョン文字列が含まれます。その値は、x402のプロトコルバージョンとは目的が異なります。署名ドメインの構造は、[ERC-3009仕様](https://eips.ethereum.org/EIPS/eip-3009)で確認してください。

## セキュリティ点検と外部サービスの信頼範囲

決済の成功判定は、サーバー側で制御してください。外部facilitatorのレスポンスも、明示的な信頼対象です。自前での運用もできる構成については、[facilitatorドキュメント](https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md)に説明されています。

- クライアントの成功メッセージで、コンテンツへのアクセスを許可しないでください。
- サーバーが宣言したチェーン・トークン・金額・受取人を比較してください。
- 秘密鍵やシードフレーズをログに残さないでください。
- 承認署名の全体も、通常のアクセスログから除外してください。
- 決済ヘッダーのサイズとリクエストの処理時間を制限してください。
- 有料レスポンスが共有キャッシュに残らないように点検してください。
- テスト用キーと本番用キーの保管場所を分けてください。
- facilitatorの障害時に、決済なしで迂回する経路を作らないでください。
- 承認の期限切れと重複リクエストを、実際にテストしてください。

実習で固定した依存関係には、セキュリティチェックの警告が残っています。`npm audit`は、zodに関するアドバイザリを報告しました。関連する内容は、[GitHubセキュリティアドバイザリ](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5)で確認してください。

```bash
npm audit
```

本番環境へのデプロイ前に、修正版とのSDKの互換性を確認してください。メジャーバージョンを強制的に置き換えると、動作が変わる可能性があります。この実習を、セキュリティ検証済みのデプロイ構成として使用しないでください。

### 自前のサーバーとCloudflare Gatewayの比較

Cloudflare Gatewayを使うと、オリジンサーバーでの検証方法が変わります。2026年9月30日付のドキュメントでは、x402 v2の使用が明記されています。次の区分は、[Cloudflare x402ドキュメント](https://developers.cloudflare.com/monetization-gateway/x402/)に基づいています。

| 区分 | この記事のExpress方式 | Cloudflare Monetization Gateway |
|---|---|---|
| クライアントの決済処理 | Expressミドルウェア | Gateway |
| オリジンサーバーでの検証箇所 | 選択したfacilitatorと連携 | `PAYMENT-CONTEXT` JWTの検証 |
| 精算結果の受け渡し | ミドルウェアの`PAYMENT-RESPONSE` | Gatewayが精算レシートを生成 |
| 追加の確認事項 | facilitatorの対応チェーンと認証 | JWTの署名検証とオリジンへのアクセス保護 |

`PAYMENT-CONTEXT`は、Gatewayとオリジンサーバーの間で使うヘッダーです。一般的なx402クライアントの署名ヘッダーではありません。オリジンでの検証は、[Cloudflare Payment validationドキュメント](https://developers.cloudflare.com/monetization-gateway/configuration/payment-validation/)に従ってください。

Gatewayを経由しても、JWTを単にデコードするだけではいけません。ドキュメントで指定されたキーを使って、署名を検証してください。ヘッダーがない場合や検証に失敗した場合は、有料コンテンツをブロックします。

## 実際の使用量と精算記録の区別

オンチェーンの送金件数だけでは、コンテンツの消費を証明できません。運用指標は、精算とレスポンスの配信を関連付けて集計してください。これは、決済フローと再送の動作に基づく設計上の提案です。

| 記録 | 答えられる問い | 追加で必要な記録 |
|---|---|---|
| 402レスポンス | 決済条件がどれだけリクエストされたか | 実際に決済されたかどうか |
| 検証成功 | 承認が有効だったか | 精算結果 |
| USDC送金 | 資産が移動したか | 注文との対応関係 |
| 注文ごとの精算完了 | 有料リクエストの決済が済んだか | レスポンスの配信状態 |
| 再配信記録 | 決済済みのリクエストを復旧したか | 新規決済と区別する識別子 |

テストリクエストは、実際の使用量の集計で区別してください。同じ注文の再試行も、別途表示します。取引量そのものを、APIの需要や収益の保証として説明してはいけません。

## Baseメインネットへの移行手順

テストネットで正常時と失敗時の経路を確認してから、メインネットを検討してください。ネットワーク文字列を変更するだけでは、移行は完了しません。基準となる手順は、[公式販売者ガイド](https://docs.x402.org/getting-started/quickstart-for-sellers#running-on-mainnet)に従います。

1. 選択したfacilitatorがBaseメインネットに対応していることを確認してください。
2. `/supported`で、v2・`exact`・`eip155:8453`への対応を確認してください。
3. 提供者が要求する認証と利用条件を設定してください。
4. サーバーとクライアントのネットワークを両方変更してください。
5. クライアントで許可するUSDCアドレスを、メインネットのアドレスに変更してください。
6. 402レスポンスの金額と、実際の受取アドレスを再度照合してください。
7. 重複リクエスト用ストアと障害復旧手順を組み込んでください。
8. 本番用の依存関係のセキュリティチェックを完了してください。
9. 設定した少額で、実際の送金とコンテンツの配信を確認してください。

マルチチェーン対応は、すべての組み合わせへの対応を意味しません。SDKとfacilitatorの対応範囲を、両方確認してください。確認の基準は、[ネットワーク・トークン対応ドキュメント](https://docs.x402.org/core-concepts/network-and-token-support)です。

## FAQ

### x402は通常のUSDC入金確認と何が違うのですか？
HTTPリクエストの過程で決済条件を伝え、署名済みの決済承認を使って再リクエストします。入金履歴を後から照合する方式とは異なり、有料リソースへのリクエストに決済手続きを結び付けます。

### x402 v2の決済ヘッダーは何ですか？
決済条件はPAYMENT-REQUIRED、決済ペイロードはPAYMENT-SIGNATUREで伝えます。精算結果はPAYMENT-RESPONSEで伝えます。

### v1にX-PAYMENT-REQUIREDヘッダーはありますか？
公式のHTTP v1仕様では、最初の決済条件をJSONレスポンス本文に含めます。決済ペイロードと精算結果には、それぞれX-PAYMENTとX-PAYMENT-RESPONSEを使います。

### /verifyが成功すれば決済は完了したのですか？
/verifyの成功は、決済承認が有効であることを意味します。この実習では、/settleの成功結果を別途確認する必要があります。

### Base SepoliaのテストUSDCを実際の収益とみなせますか？
Circleは、テストネットのUSDCには金銭的価値がないと明記しています。テストネットでの精算は、実際の売上とは分けて記録してください。

### 受取サーバーに秘密鍵を入れる必要がありますか？
この例の受取サーバーには、公開の受取アドレスだけを設定します。決済承認に使う秘密鍵は、決済クライアントで読み込みます。

### 0.001 USDCのamount値はいくつですか？
USDCの小数点以下6桁を適用すると、最小単位では1000です。v2の決済条件には文字列"1000"を使います。

### トランザクションレシートが成功を示していれば、USDCの受け取りも確認できたのですか？
トランザクションの成功ステータスだけでは不十分です。公式USDCコントラクトのTransferイベントで、支払者・受取人・金額を照合してください。

### 同じ決済リクエストを再送しても安全ですか？
同一の承認nonceの再利用防止と、注文の重複防止は別です。再試行する前に既存の決済状態を確認し、注文ごとの冪等性処理を実装してください。

### PAYMENT-RESPONSEがなければ、もう一度決済すればよいですか？
レスポンスの消失やタイムアウトの場合、精算結果が不明確な可能性があります。新しい承認を作るのではなく、既存のリクエストの精算状態を確認してください。

### x402では特定の会社のfacilitatorだけを使わなければなりませんか？
公式の構造では、外部facilitatorの利用と自前での運用の両方が認められています。選んだ実装が、必要なプロトコルバージョン・決済方式・チェーンをサポートしているか確認してください。

### AIエージェントの支出も自動的に制限されますか？
この例では、リクエストごとの決済条件だけを制限します。繰り返し呼び出すことによる累積支出は、別途予算ポリシーで管理してください。

### Cloudflare PAYMENT-CONTEXTは標準の決済署名ヘッダーですか？
PAYMENT-CONTEXTは、Gatewayがオリジンサーバーに渡すJWTです。クライアントのPAYMENT-SIGNATUREとは用途が異なります。

### サンプルをそのままメインネットにデプロイしてもよいですか？
このコードはテスト用の最小構成です。本番環境へのデプロイ前に、依存関係のセキュリティ警告、重複リクエストの処理、facilitatorの認証と実際の決済を検証してください。

## Sources

- [x402 Protocol Specification v2](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md)
- [x402 HTTP Transport v1](https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md)
- [x402 HTTP Transport v2](https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md)
- [x402 Migration Guide: V1 to V2](https://docs.x402.org/guides/migration-v1-to-v2)
- [x402 Facilitator の役割と責任](https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md)
- [x402 Quickstart for Sellers](https://docs.x402.org/getting-started/quickstart-for-sellers)
- [x402 Quickstart for Buyers](https://docs.x402.org/getting-started/quickstart-for-buyers)
- [x402 公式 TypeScript fetch クライアントの例](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch)
- [x402 Express ミドルウェアの実装](https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts)
- [x402Client の決済条件の選択とポリシーの実装](https://github.com/x402-foundation/x402/blob/main/typescript/packages/core/src/client/x402Client.ts)
- [x402 ExactEvmScheme サーバーの価格変換の実装](https://github.com/x402-foundation/x402/blob/main/typescript/packages/mechanisms/evm/src/exact/server/scheme.ts)
- [Circle USDC コントラクトアドレス](https://developers.circle.com/stablecoins/usdc-contract-addresses)
- [Base メインネットと Base Sepolia のネットワーク情報](https://docs.base.org/get-started/connect-to-base)
- [Circle Testnet Faucet](https://faucet.circle.com/)
- [viem getTransactionReceipt](https://viem.sh/docs/actions/public/getTransactionReceipt)
- [viem parseEventLogs](https://viem.sh/docs/contract/parseEventLogs)
- [ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20)
- [ERC-3009 Transfer With Authorization](https://eips.ethereum.org/EIPS/eip-3009)
- [x402 Payment-Identifier 冪等性拡張](https://docs.x402.org/extensions/payment-identifier)
- [Cloudflare Monetization Gateway x402 protocol](https://developers.cloudflare.com/monetization-gateway/x402/)
- [Cloudflare Monetization Gateway Payment validation](https://developers.cloudflare.com/monetization-gateway/configuration/payment-validation/)
- [x402 Networks & Token Support](https://docs.x402.org/core-concepts/network-and-token-support)
- [GitHub セキュリティアドバイザリ: Zod の大規模コレクションの検証によるメモリ枯渇](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5)

## Images

![濃いグレーのセーターを着た男性が、サーバーラックの棚に置かれたタブレットを確認している。](https://injoys.com/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzM4MDcsInB1ciI6ImJsb2JfaWQifX0=--49c502fd8da16158a617c3586dfa81ced4bc95df/ai-f0ae2b5c.webp)
![窓辺で女性が平らに開いたノートパソコンを操作し、画面には一覧と2本の経路が合流するフロー図が表示されている。](https://injoys.com/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzM4MTMsInB1ciI6ImJsb2JfaWQifX0=--4e70727f132519277c68d1c6279d95585a936549/ai-3dbd1c62.webp)