Serverless Operations, inc

>_cd /blog/id_z1-6z0f4uhwn

title

Amazon CloudFront と AWS WAF AI traffic monetization で作るAI Agent向け HTTP402環境

なぜ今「リクエスト単位の課金」が必要になったのか

過去30年間、Webは「コンテンツを人間の注目(アテンション)と交換する」というシンプルな経済モデルで動いてきました。その基本は、広告・サブスクリプション・ECという形で収益化され、インターネットの発展を支えてきました。

ところが、AIエージェントが主要なインターネット利用者になり始めたことで、このモデルが崩れつつあります。エージェントは広告を見ないし、使いたいツールすべてに月額課金を維持したりもありません。ページを一度読んで必要なものを取り、記憶します。実際、AIクローラーは1回の訪問で、コンテンツを数百回から数万回リクエストするケースもあり、当然広告をクリックすることもありません。つまり「人間に見てもらう」ことを前提にした従来の収益化は、機械が主役になった世界では成立しなくなっていきます。

そこで浮上するのが「すべてに対する従量課金」という発想です。ソフトウェアにとって自然な課金の単位は、「席(seat)」や「月」ではなく、リクエスト・トークン・成果になります。Web検索1回あたり数セント、アップロードエンドポイントに基本料+1MBあたりいくら、解決したサポート案件1件あたりいくら(しかも成功時のみ課金)——こうした細かい価格設定こそが、AIエージェントが取得となる新しいインターネット時代の課金モデルになります。

HTTP 402

技術的には、少額決済(マイクロペイメント)は長年「割に合わない」ものでした。従来の決済レールは手数料が高く、決済確定に時間もかかります。ある金額を下回ると、支払いを回収するコスト自体が支払い額を上回ってしまいます。だから多くのコンテンツは課金を諦めて広告に頼り、APIやクラウドは「サインアップ→APIキー発行→従量課金」という、身元のわかった相手にしか使えない仕組みに閉じていた。これは従来の苦ジレット決済が人による認証を前提としている点とも深くかかわっています。

この状況を変えるのがステーブルコインです。USDCのようなステーブルコインは、極小額をインターネット越しに、ごくわずかな手数料で、1秒未満で送金・決済できます。これにより1セントの何分の1という単位の取引が、現実的に成立するようになります。

こうした機械間決済の共通言語として設計されたのが x402 (Payment Required) です。仕組みはシンプルで、通常のHTTPリクエストの中で完結します。クライアントが課金対象のリソースを要求すると、サーバーはコンテンツを返す代わりに「402」と、価格・受け入れる資産・支払い先を記した小さなペイロードを返します。クライアントは支払いを行い、その証明を添えて同じリクエストを送り直します。ステーブルコイン仲介者が支払いを検証し、サーバーがリソースを返します。チェックアウトページへのリダイレクトも、別の決済APIの呼び出しも不要です。

x402が機械間決済に向いている理由は2つあります。ひとつは、プロトコルのオーバーヘッドがほぼないため、1セントの何分の1という極小額でも扱えることがかのうです。もうひとつは、支払いそのものが認証情報になるため、買い手が売り手のアカウントを事前に持つ必要がないことです。エージェントは、サインアップもAPIキーも事前の関係も無しに、リソースを要求し、価格を告げられ、支払い、応答を受け取ることができます。

AWS WAF の「AI traffic monetization」が担う役割

x402は優れたプロトコルですが各コンテンツ提供者が自前で「402の返却」「支払い検証」「ステーブルコインの取り扱い」「オンチェーン決済」を実装するのは大変です。多くの開発者が本当に欲しいのは、「このエンドポイントに0.001ドルを課金する」というルールを1つ書くことだけです。

AWS WAF の AI traffic monetization は、まさにその実装を肩代わりします。CloudFrontのエッジネットワーク上でx402をネイティブに扱い、次のことを実現すします。

  1. エッジで課金を強制する。 リクエストがオリジンサーバーに到達する前に、エッジで支払いを検証します。支払っていないリクエストはオリジンに届かないため、課金の仕組みであると同時にオリジンの保護にもなります。
  2. ボットにだけ課金し、人間は素通しできる。 これはWAFのBot Controlに追加された新しいアクションで、ボットと判定されたリクエストに対して従来の「許可/ブロック」に加えて「Monetize(課金)」を選べます。Monetizeを割り当てると、支払いのないリクエストには402が返ります。Bot Controlのラベルと組み合わせれば、機械には402を、人間のブラウザには通常のコンテンツを、と出し分けるこができます。
  3. 成功したリクエストにだけ課金する。 検証・コンテンツ取得・決済はリクエストパス内で同期的に行われ、オリジンが成功(2xx)を返した場合にのみ、Coinbaseのx402 Facilitatorを通じてオンチェーンで決済が確定します。オリジンが4xx/5xxを返した場合は決済がスキップされ、支払い側には課金されません。
  4. 自前の決済基盤も、買い手のオンボーディングも不要。 提供者はダッシュボードやCLI・Terraformで課金ルールを書くだけでよく、貯まったステーブルコインはそのまま使うか、法定通貨に換金できます。買い手はサインアップもAPIキーも事前の関係も無しに支払ることができるため、事前登録不要のマイクロ決済が実現可能です。

さっそくやってみる

このシナリオは少し手順が多く煩雑です。ページのリクエスト側、サーバ(オリジン側)にそれぞれ別々のテスト用ステーブルコインウォレットを準備した後、HTTP402をレスポンスとして戻す、Amazon CloudFront + AWS WAF + Amazon S3 (OAC) 環境を構築します。

  • Step 0 — 支払い側(ウォレットとテストUSDC)の準備
  • Step 1 — S3 + CloudFront で「守るコンテンツ」を用意
  • Step 2 — WAF web ACL を作成(Bot Control 有効化)
  • Step 3 — 課金設定(MonetizationConfig)を追加
  • Step 4 — 有料パスに 402 を返す Monetize ルールを追加
  • Step 5 — web ACL を CloudFront に紐づけ
  • Step 6 — 402 レスポンスを確認
  • Step 7 — テストUSDCで支払い、200 でコンテンツ取得
  • Step 8 — 決済をブロックエクスプローラーで確認

前提として、AWS アカウントと AWS CLI v2、CloudFront / WAF / S3 を操作できる IAM 権限、そして Python 3.10+ が使える環境(今回は WSL / Ubuntu)を想定しています。WAFの Monetize アクションは CloudFront に紐づく web ACL でのみ使うことができるため、WAF はすべて--scope CLOUDFRONT --region us-east-1 で操作します。

x402 の決済では、**買い手(支払い元)と売り手(受け取り先)を別のウォレットにする**必要があります。テスト用に同一ウォレットを使うと自己送金として拒否されます。

## Step 0: 支払い側の準備(テストネット)
まず、支払いフローを最後まで通すために、買い手側のウォレットとテスト資金を用意します。

まずPythonのvenv環境を作成します。

sudo apt install -y python3-full python3-venv
mkdir -p ~/cloudfront402 && cd ~/cloudfront402
python3 -m venv venv
source venv/bin/activate   # プロンプトの先頭が (venv) になる

次に必要ライブラリをインストールします。

x402 のクライアントは、HTTP クライアント用( requests )と EVM 署名用( evm )の extra を両方入れる必要があります。

pip install --upgrade pip
pip install "x402[requests]" "x402[evm]" eth-account requests web3

買い手と売り手用ステーブルコインのウォレットを2つ作ります。USDCにはテスト用トークンが準備されていますので、実際に支払いテストを行うことが可能ですが、事前にトークンをチャージしておく必要はありません。

cat > gen_wallet.py << 'EOF'
from eth_account import Account
acct = Account.create()
print("address:", acct.address)
print("private key:", acct.key.hex())
EOF

2回実行して、2つのウォレットを作ります。

python3 gen_wallet.py   # 1つ目 → 売り手(受け取り先)
python3 gen_wallet.py   # 2つ目 → 買い手(支払い元)

以下をメモしておきます。

address: 0x21c5ee8eE46C66Deb7888f4bAdD1c6D7C76ad13d
private key: 2dde1135d727a767e713df804986da9b09fccfec7ddc01d7079084757c376168
address: 0x093D05C4314b0671C40925E90EE0a581a461Dd71
private key: 8ca847fa8cf08b26a541c0e99b875d66508b39acd0cd792ca1372528d420dd38

環境変数に以下の値を保存しておきます。

cat > .env << 'EOF'
SELLER_ADDRESS=0x21c5ee8eE46C66Deb7888f4bAdD1c6D7C76ad13d
BUYER_ADDRESS=0x093D05C4314b0671C40925E90EE0a581a461Dd71
BUYER_PRIVATE_KEY=8ca847fa8cf08b26a541c0e99b875d66508b39acd0cd792ca1372528d420dd38
EOF

AddressおよびPrivate_keyは皆さんの 環境ごとに異なります ので注意してください。

次に買い手側ウオレットにテスト用USDCトークンを入れます。ブラウザで https://faucet.circle.com/ にアクセスします。Networkで Base Sepoliaを選択して、買い手側アドレスを入れるとテスト用20USDCトークンが送金されます。

https://sepolia.basescan.orgにアクセスして買い手側ウォレットのアドレスを入力すると着金が確認できます。

Step 1: S3 + CloudFront で「守るコンテンツ」を用意

課金対象になるコンテンツを用意しまする。この段階では誰でも見られる普通の配信をまずはセットアップします。

export AWS_REGION=us-east-1               # CloudFront 関連は us-east-1 に寄せる
export BUCKET="x402-demo-$(date +%s)"
echo "BUCKET=$BUCKET"
aws s3api create-bucket --bucket "$BUCKET" --region us-east-1

mkdir -p site/premium
cat > site/index.html << 'EOF'
<!doctype html>
<html><head><meta charset="utf-8"><title>Free page</title></head>
<body><h1>Free content</h1><p>誰でも読めるページです。</p></body></html>
EOF
cat > site/premium/article.html << 'EOF'
<!doctype html>
<html><head><meta charset="utf-8"><title>Premium article</title></head>
<body><h1>Premium content</h1><p>これはx402で課金する有料記事です。</p></body></html>
EOF

aws s3 cp site/ "s3://$BUCKET/" --recursive

バケットが作成され2つのHTMLがPutされます。

{
    "Location": "/x402-demo-1786175291",
    "BucketArn": "arn:aws:s3:::x402-demo-1786175291"
}
upload: site/index.html to s3://x402-demo-1786175291/index.html
upload: site/premium/article.html to s3://x402-demo-1786175291/premium/article.html

次にOAC(Origin Access Control)を作成を作成してCloudFront経由でアクセス可能にします。

cat > oac.json << 'EOF'
{
  "Name": "x402-demo-oac",
  "OriginAccessControlOriginType": "s3",
  "SigningBehavior": "always",
  "SigningProtocol": "sigv4"
}
EOF

aws cloudfront create-origin-access-control --origin-access-control-config file://oac.json

# OAC ID を取得
export OAC_ID=$(aws cloudfront list-origin-access-controls \
  --query "OriginAccessControlList.Items[?Name=='x402-demo-oac'].Id" --output text)
echo "OAC_ID=$OAC_ID"
OAC_ID=E22NYU2IIRWGZ5

表示された OAC_ID は環境変数に保存されています。次にCloudFrontのディストリビューションを作成します。

cat > dist-config.json << EOF
{
  "CallerReference": "x402-demo-$(date +%s)",
  "Comment": "x402 monetization demo",
  "Enabled": true,
  "DefaultRootObject": "index.html",
  "Origins": {
    "Quantity": 1,
    "Items": [
      {
        "Id": "s3-origin",
        "DomainName": "${BUCKET}.s3.us-east-1.amazonaws.com",
        "OriginAccessControlId": "${OAC_ID}",
        "S3OriginConfig": { "OriginAccessIdentity": "" }
      }
    ]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "s3-origin",
    "ViewerProtocolPolicy": "redirect-to-https",
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "AllowedMethods": {
      "Quantity": 2,
      "Items": ["GET", "HEAD"],
      "CachedMethods": { "Quantity": 2, "Items": ["GET", "HEAD"] }
    }
  }
}
EOF

aws cloudfront create-distribution --distribution-config file://dist-config.json

出力から Distribution.IdDistribution.DomainName を控えて、変数にセットします。

export DIST_ID="E43BE5DBYYRVS"
export DIST_DOMAIN="dc16tqm0myzcy.cloudfront.net"

それぞれの値は皆さんの環境ごとに異なりますので適宜書き換えてください。

次にS3 バケットポリシー(OAC からの読み取りを許可)を作成して先ほど作成したS3バケットにアタッチします。

export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

cat > bucket-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOAC",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::${BUCKET}/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::${ACCOUNT_ID}:distribution/${DIST_ID}"
        }
      }
    }
  ]
}
EOF

aws s3api put-bucket-policy --bucket "$BUCKET" --policy file://bucket-policy.json

数分待った後動作確認を行います。

aws cloudfront get-distribution --id "$DIST_ID" --query "Distribution.Status" --output text

curl -I "https://$DIST_DOMAIN/index.html"
curl -I "https://$DIST_DOMAIN/premium/article.html"

以下の様に2つのHTMLに対してHTTP200が戻っていれば成功です。

HTTP/2 200
content-type: text/html
content-length: 167
date: Sat, 08 Aug 2026 07:56:01 GMT
last-modified: Sat, 08 Aug 2026 07:48:32 GMT
etag: "3f11a5e7ebd5d39901c9cb2a33ba9cce"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 3f3fb708ebdc38166393802d73c4aee6.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P4
x-amz-cf-id: kZbBEkPmxU16XTe62VHpoiKCuekl5Ipt3l6HvKn0SDrkZUvOa_zf0Q==

HTTP/2 200
content-type: text/html
content-length: 189
date: Sat, 08 Aug 2026 07:56:02 GMT
last-modified: Sat, 08 Aug 2026 07:48:32 GMT
etag: "c00464a5c8a6a8cd050589aab3e37830"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 d83ceae2f2ed0c829779c7f585d230f0.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P4
x-amz-cf-id: Wyerz9-sS0gseFrIlDWEDX5h7xsDPB8OSNNdBZ2EYom5F84DvkAcjA==

Step 2: WAF web ACL を作成(Bot Control)

Monetize は Bot Control のアクションなので、Bot Control マネージドルールを含む web ACL を作ります。今回は Common モード(基本的なボット検出、安価)を使います。

まずルール定義ファイルを作成します。

cat > rules.json << 'EOF'
[
  {
    "Name": "BotControlCommon",
    "Priority": 0,
    "Statement": {
      "ManagedRuleGroupStatement": {
        "VendorName": "AWS",
        "Name": "AWSManagedRulesBotControlRuleSet",
        "ManagedRuleGroupConfigs": [
          { "AWSManagedRulesBotControlRuleSet": { "InspectionLevel": "COMMON" } }
        ]
      }
    },
    "OverrideAction": { "Count": {} },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "BotControlCommon"
    }
  }
]
EOF

WAFにルールをアタッチします。

aws wafv2 create-web-acl \
  --name x402-demo-acl \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --default-action Allow={} \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=x402demo \
  --rules file://rules.json

# ID と ARN を取得
export WEBACL_ID=$(aws wafv2 list-web-acls --scope CLOUDFRONT --region us-east-1 \
  --query "WebACLs[?Name=='x402-demo-acl'].Id" --output text)
export WEBACL_ARN=$(aws wafv2 list-web-acls --scope CLOUDFRONT --region us-east-1 \
  --query "WebACLs[?Name=='x402-demo-acl'].ARN" --output text)
echo "WEBACL_ID=$WEBACL_ID"
echo "WEBACL_ARN=$WEBACL_ARN"

Step 3: 課金設定(MonetizationConfig)を追加

上記で設定したWAF用WebACLに「支払いのルールブック」を設定します。受け入れネットワーク・受け取りウォレット・ベース価格・通貨モードを定義していきます。先ほど作成した売り手側ウォレット指定することでトークンを受け取ることができるようになります。

テストネット( BASE_SEPOLIA )、受け取り先は**売り手ウォレット**、ベース価格 $0.001、 CurrencyModeTESTを指定します。

export SELLER_ADDRESS=$(grep '^SELLER_ADDRESS=' .env | cut -d= -f2)

cat > monetization-config.json << EOF
{
  "CurrencyMode": "TEST",
  "CryptoConfig": {
    "PaymentNetworks": [
      {
        "Chain": "BASE_SEPOLIA",
        "WalletAddress": "${SELLER_ADDRESS}",
        "Prices": [ { "Amount": "0.001", "Currency": "USDC" } ]
      }
    ]
  }
}
EOF

CurrencyMode を指定しないとデフォルトが REAL(本番=実USDC)になるため、テストのときは必ず TEST を明示する必要があります。 Amount は最大3桁の小数の文字列で USDC 建て(`"0.001"` = $0.001/リクエスト)を指定可能です。

web ACL の更新には LockToken が必要です。これはgetの度に代わりますのでコマンドは一気に実行する必要があります。また update-web-acl は全体置換なので、既存の --rules なども一緒に渡します。

export LOCK_TOKEN=$(aws wafv2 get-web-acl \
  --name x402-demo-acl --scope CLOUDFRONT --region us-east-1 \
  --id "$WEBACL_ID" --query "LockToken" --output text)

aws wafv2 update-web-acl \
  --name x402-demo-acl \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --id "$WEBACL_ID" \
  --lock-token "$LOCK_TOKEN" \
  --default-action Allow={} \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=x402demo \
  --rules file://rules.json \
  --monetization-config file://monetization-config.json

Step 4: 有料パスに 402 を返す Monetize ルールを追加

/premium/ で始まるパスにだけ Monetize を適用します。無料ページは default action(Allow)でそのまま通るように残します。パス一致は ByteMatchStatement を使います。ここで一つ注意点があり、**`SearchString` は base64 エンコードして渡す**必要があります(CLI が blob フィールドとして base64 を期待するため)。`/premium/` の base64 は L3ByZW1pdW0vです。

echo -n "/premium/" | base64   # → L3ByZW1pdW0v を確認

cat > rules.json << 'EOF'
[
  {
    "Name": "BotControlCommon",
    "Priority": 0,
    "Statement": {
      "ManagedRuleGroupStatement": {
        "VendorName": "AWS",
        "Name": "AWSManagedRulesBotControlRuleSet",
        "ManagedRuleGroupConfigs": [
          { "AWSManagedRulesBotControlRuleSet": { "InspectionLevel": "COMMON" } }
        ]
      }
    },
    "OverrideAction": { "Count": {} },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "BotControlCommon"
    }
  },
  {
    "Name": "MonetizePremiumPath",
    "Priority": 1,
    "Statement": {
      "ByteMatchStatement": {
        "SearchString": "L3ByZW1pdW0v",
        "FieldToMatch": { "UriPath": {} },
        "TextTransformations": [ { "Priority": 0, "Type": "NONE" } ],
        "PositionalConstraint": "STARTS_WITH"
      }
    },
    "Action": { "Monetize": { "PriceMultiplier": "1" } },
    "VisibilityConfig": {
      "SampledRequestsEnabled": true,
      "CloudWatchMetricsEnabled": true,
      "MetricName": "MonetizePremiumPath"
    }
  }
]
EOF

PriceMultiplier はベース価格に掛ける倍率(1〜100の整数、文字列指定)です。 1 ならベース価格そのまま(0.001 USDC)となりますが、 3 にすれば 0.003 USDC になるという使い方です。

つまりBaseの請求単価を決めておいて、コンテンツごとに倍率を変える、といった使い方が可能です。

ではルールを適応します。

export LOCK_TOKEN=$(aws wafv2 get-web-acl \
  --name x402-demo-acl --scope CLOUDFRONT --region us-east-1 \
  --id "$WEBACL_ID" --query "LockToken" --output text)

aws wafv2 update-web-acl \
  --name x402-demo-acl \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --id "$WEBACL_ID" \
  --lock-token "$LOCK_TOKEN" \
  --default-action Allow={} \
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=x402demo \
  --rules file://rules.json \
  --monetization-config file://monetization-config.json

Step 5: web ACL を CloudFront に紐づけ

WAFに対して行った設定をCloudFrontと紐づけます。

# 現在の設定と ETag を取得
aws cloudfront get-distribution-config --id "$DIST_ID" > dist-current.json
export ETAG=$(python3 -c "import json;print(json.load(open('dist-current.json'))['ETag'])")

# WebACLId を差し込んで DistributionConfig だけ取り出す
python3 << EOF
import json
data = json.load(open("dist-current.json"))
config = data["DistributionConfig"]
config["WebACLId"] = "${WEBACL_ARN}"
json.dump(config, open("dist-updated.json", "w"))
EOF

# 反映
aws cloudfront update-distribution \
  --id "$DIST_ID" \
  --distribution-config file://dist-updated.json \
  --if-match "$ETAG"

Step 6: 402 レスポンスを確認

ではテストです。無料ページと有料ページを叩き比べます。

以下の様になるはずです。1個目はHTTP200でページ内容が、2個目はHTTP402で支払い要求が出てきます。

HTTP/2 200
content-type: text/html
content-length: 167
date: Sat, 08 Aug 2026 07:56:01 GMT
last-modified: Sat, 08 Aug 2026 07:48:32 GMT
etag: "3f11a5e7ebd5d39901c9cb2a33ba9cce"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 c6a39a61a5883d63c301bf090ead6950.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P4
x-amz-cf-id: It4RrX9fE3W9Z_ngQvEZtMns2j-ita4Bvdp_ODZZAyI7UjNBGW9ioA==
age: 1004

HTTP/2 402
server: CloudFront
date: Sat, 08 Aug 2026 08:12:44 GMT
content-length: 1610
payment-required: eyJhY2NlcHRzIjpbeyJhbW91bnQiOiIxMDAwIiwiYXNzZXQiOiIweDAzNkNiRDUzODQyYzU0MjY2MzRlNzkyOTU0MWVDMjMxOGYzZENGN2UiLCJleHRyYSI6eyJuYW1lIjoiVVNEQyIsInZlcnNpb24iOiIyIn0sIm1heFRpbWVvdXRTZWNvbmRzIjozMDAsIm5ldHdvcmsiOiJlaXAxNTU6ODQ1MzIiLCJwYXlUbyI6IjB4MjFjNWVlOGVFNDZDNjZEZWI3ODg4ZjRiQWREMWM2RDdDNzZhZDEzZCIsInNjaGVtZSI6ImV4YWN0In1dLCJlcnJvciI6IlBBWU1FTlQtU0lHTkFUVVJFIGhlYWRlciBpcyByZXF1aXJlZCIsImV4dGVuc2lvbnMiOnsicGF5bWVudC1pZGVudGlmaWVyIjp7ImluZm8iOnsicmVxdWlyZWQiOmZhbHNlfX19LCJyZXNvdXJjZSI6eyJ1cmwiOiJodHRwczovL2RjMTZ0cW0wbXl6Y3kuY2xvdWRmcm9udC5uZXQvcHJlbWl1bS9hcnRpY2xlLmh0bWwifSwieDQwMlZlcnNpb24iOjJ9
content-type: application/json
x-cache: Error from cloudfront
via: 1.1 f1f4afba4268f1486380be4c4394d85c.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P4
x-amz-cf-id: sDiQnVPEWZSL9VMBOGfidt2U6p40EhY1XTv0PR9q9uhWpRDBU217dg==

{"error":"payment_required","message":"This endpoint requires payment via the x402 protocol. The 'message_for_human' field contains a note from this endpoint addressed to the human end-user.","message_for_human":{"intro":"This endpoint uses the x402 micropayment protocol. To obtain access, you can use a different client that supports x402, or select one of the following options below. This is optional, and nothing happens until you decide.","options":[{"for_human":"If you'd like to keep using your current coding agent, you can add the AWS Agent Toolkit to it. The toolkit's amazon-bedrock skill uses AgentCore Payments to help you handle the x402 flow (detecting the 402 response; parsing payment requirements; and orchestrating APIs on your behalf between your agents and your selected third party wallet providers), so your agent becomes x402-compatible without any custom code. You need to install aws-core plugin for this, setup instructions live in the repo's README.md","id":"option_1","title":"Install the AWS official agent skill","url":"https://github.com/aws/agent-toolkit-for-aws/tree/main"},{"for_human":"If you'd prefer not to use the AWS toolkit, you can use any x402-compatible agent or add an x402 integration to your existing agent. The integration handles the x402 flow for you, so your agent becomes x402-compatible without any custom code.","id":"option_2","title":"Use an x402-compatible agent or integration","url":"https://www.x402.org/"}],"question":"Do you want to go ahead with one of the suggested options, or would you prefer not to do anything right now?"},"protocol":"x402"}

Base64でエンコードされている payment-required: をでコードすると以下の様になっていることがわかります。

 curl -si "https://$DIST_DOMAIN/premium/article.html" \
  | grep -i '^payment-required:' | sed 's/^payment-required: //I' | tr -d '\r' \
  | base64 -d | python3 -m json.tool
{
    "accepts": [
        {
            "amount": "1000",
            "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
            "extra": {
                "name": "USDC",
                "version": "2"
            },
            "maxTimeoutSeconds": 300,
            "network": "eip155:84532",
            "payTo": "0x21c5ee8eE46C66Deb7888f4bAdD1c6D7C76ad13d",
            "scheme": "exact"
        }
    ],
    "error": "PAYMENT-SIGNATURE header is required",
    "extensions": {
        "payment-identifier": {
            "info": {
                "required": false
            }
        }
    },
    "resource": {
        "url": "https://dc16tqm0myzcy.cloudfront.net/premium/article.html"
    },
    "x402Version": 2
}

"error": "PAYMENT-SIGNATURE header is required" がポイントです。要はHTTPヘッダに送金の証(署名データ)が含まれていないためエラーとなっています。

Step 7: テストUSDCで支払い、200 でコンテンツ取得

x402 のクライアントライブラリが、402 の検知・マニフェストの解析・EIP-3009 署名・`PAYMENT-SIGNATURE` ヘッダーの構築・再送までやってくれます。支払いには**買い手ウォレット**の秘密鍵を使います。

export EVM_PRIVATE_KEY=$(grep '^BUYER_PRIVATE_KEY=' .env | cut -d= -f2)
echo "payer key starts with: ${EVM_PRIVATE_KEY:0:6}..."
cat > pay_client.py << 'EOF'
import os
import requests
from eth_account import Account
from x402 import x402ClientSync
from x402.http import (
    decode_payment_required_header,
    encode_payment_signature_header,
    decode_payment_response_header,
    PAYMENT_REQUIRED_HEADER,
    PAYMENT_SIGNATURE_HEADER,
    PAYMENT_RESPONSE_HEADER,
)
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact.register import register_exact_evm_client

URL = "https://<YOUR_DIST_DOMAIN>/premium/article.html"

client = x402ClientSync()
account = Account.from_key(os.getenv("EVM_PRIVATE_KEY"))
register_exact_evm_client(client, EthAccountSigner(account))
print("Paying from wallet:", account.address)

# 1回目: 402 とマニフェスト取得
r1 = requests.get(URL)
pr_header = r1.headers.get(PAYMENT_REQUIRED_HEADER) or r1.headers.get("payment-required")
required = decode_payment_required_header(pr_header)

# 支払いペイロードを作成し、ヘッダーにエンコードして再送
payload = client.create_payment_payload(required)
sig_header = encode_payment_signature_header(payload)
r2 = requests.get(URL, headers={PAYMENT_SIGNATURE_HEADER: sig_header})

print("Status:", r2.status_code)
print("Body:", r2.text)

resp = r2.headers.get(PAYMENT_RESPONSE_HEADER) or r2.headers.get("payment-response")
if resp:
    print("Settlement:", decode_payment_response_header(resp))
EOF

上記の<YOUR_DIST_DOMAIN> を自分のディストリビューションのドメインに置き換えて実行してください。

python3 pay_client.py を実行すると以下の様に支払いが行われます。

Paying from wallet: 0x093D05C4314b0671C40925E90EE0a581a461Dd71
Status: 200
Body: <!doctype html>
<html><head><meta charset="utf-8"><title>Premium article</title></head>
<body><h1>Premium content</h1><p>ããã¯x402ã§èª²éããææè¨äºã§ãã</p></body></html>

Settlement: success=True error_reason=None error_message=None payer='0x093D05C4314b0671C40925E90EE0a581a461Dd71' transaction='0xfd3c66e01ab504f0ce04241c75f5c0b6efaaf785937a3426aa43355300294de1' network='eip155:84532' amount=None extensions=None extra=None

成功すると Status: 200 になり、有料記事の HTML が返ります。`Settlement:` にオンチェーンのトランザクションハッシュと success: true が出ています。

これで、**402 → 署名付き支払い → facilitator 検証 → オンチェーン決済 → 200 でコンテンツ取得**という一連が完走しています。

表示されている transaction をメモってブラウザで https://sepolia.basescan.org/tx にアクセスを行い検索してみます。支払いが正しく成功していることがわかります。

WAFの方では Monetize というルールが作成されていることがわかります。

Written by
編集部

亀田 治伸

Kameda Harunobu

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-