CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/Claude Code
Claude Code/2026-05-01中級

Claude Code で Stripe Webhook をローカルで安全にテストする3つの実用パターン

Stripe Webhook をローカルで再現するときに詰まりやすい署名検証・冪等性・再送のテストを、Claude Code と組み合わせて安全に進める3パターンを実装コード付きで解説します。

claude-code129stripe6webhook3tutorial3

Stripe の Webhook を実装するときに最初にぶつかる壁は、ローカルで起こした失敗をどうやって本番と同じ条件で再現するかという点です。stripe listen で localhost に転送するところまでは公式ドキュメントが教えてくれますが、署名検証の落とし穴・冪等性の検証・本物のイベントの再生まで一気通貫で扱った日本語記事は意外と少ないのが現状ではないでしょうか。

私自身、4 サイトで Stripe Checkout を運用していて、リファクタの度に「あれ、このパスでイベント取りこぼしてないか?」と冷や汗をかいた経験があります。ここではClaude Code を組み合わせて Webhook ハンドラを安全にテストする 3 つの実用パターンを、実際に動くコードと一緒にまとめます。

パターン1: Stripe CLI を stripe listen でローカルに転送する

公式の標準パターンですが、まずは私が実際に踏んだ失敗から見ていきます。

# 失敗しがちな例(署名検証で 400 を返す)
stripe listen --forward-to localhost:3000/api/webhook

これだけだと一見動いているように見えますが、ハンドラ側で署名を process.env.STRIPE_WEBHOOK_SECRET から読んでいる場合、本番用シークレットと stripe listen が発行する一時シークレット(同じ whsec_ 始まりでも別物)が一致せず、署名検証で必ず 400 を返してしまいます。

正しくは stripe listen の出力する一時シークレットを .env.local に書き換えて開発サーバーを起動します。

# ✅ 正しい例
stripe listen --print-secret > .stripe-secret.txt
# 出力された whsec_... を .env.local の STRIPE_WEBHOOK_SECRET にセットしてから
# 別ターミナルで stripe listen --forward-to を起動する
stripe listen --forward-to localhost:3000/api/webhook

Claude Code に stripe listen の起動と環境変数の書き換えをまとめてやらせる場合、私は次のような短いプロンプトを使っています。

.env.local の STRIPE_WEBHOOK_SECRET を `stripe listen --print-secret` の出力で
書き換えて、開発サーバーを起動してください。終了時にシークレットを元に戻すこと。

この「終了時に元に戻すこと」を入れておくと、git の差分にローカル用シークレットが混ざる事故を防げます。

パターン2: 本物のペイロードをファイル化して再生する

stripe events resend <event_id> は本番の過去イベントを再送できる便利なコマンドです。ただし「同じイベントを 100 回連投したい」「ネット環境を切ってから再生したい」「CI 上で実行したい」場合には向きません。

そんなときは Stripe Dashboard から失敗したイベントの JSON をダウンロードし、ローカルから自前の署名付きで再生する方法が便利です。

// scripts/replay-webhook.ts
//
// 何を解決するコードか:
//   保存した本物の Webhook ペイロードを、ローカルの開発サーバーへ
//   正しい署名付きで再送する。Stripe CLI が起動していなくても動く。
import fs from "node:fs";
import crypto from "node:crypto";
 
async function main() {
  const payload = fs.readFileSync(
    "./fixtures/checkout-completed.json",
    "utf8",
  );
  const secret = process.env.STRIPE_WEBHOOK_SECRET;
  if (!secret) throw new Error("STRIPE_WEBHOOK_SECRET is required");
 
  const timestamp = Math.floor(Date.now() / 1000);
  const signedPayload = `${timestamp}.${payload}`;
  const signature = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex");
  const sigHeader = `t=${timestamp},v1=${signature}`;
 
  const res = await fetch("http://localhost:3000/api/webhook", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Stripe-Signature": sigHeader,
    },
    body: payload,
  });
  console.log(res.status, await res.text());
}
 
main().catch((err) => {
  console.error(err);
  process.exit(1);
});

期待する出力:

200 {"received":true}

なぜ自前で署名するかというと、Stripe CLI に依存せず固定の secret で署名できるので、CI 環境でもオフラインでも同じスクリプトが動く点が効きます。Claude Code に「失敗時のレスポンスをログに残す形で書き換えてください」と頼めば、ハンドラ側の問題を切り分けるための再現スクリプトとしてリポジトリに残ります。

ペイロードは fixtures/ に種類別で置いておくと、replay-webhook.ts checkout-completed のように引数で切り替えられます。実プロジェクトでは payment_intent.succeeded customer.subscription.updated checkout.session.completed の 3 つを揃えておくと困らないと感じています。

パターン3: 冪等性とリトライをまとめてテストする

Stripe は同じ event.id を最大 3 日間で複数回送ってくる仕様です。本番で痛い目に遭わないために、冪等性(Idempotency)はローカルで先に検証しておきたい挙動です。

私が使っている確認手順は次の通りです。

  • パターン2 のスクリプトを 5 連続で実行する
  • ハンドラがデータベース(または KV)に重複レコードを作っていないか確認する
  • 1 回目で意図的に失敗させ(例: 通知メール送信失敗)、2 回目で成功する経路をシミュレートする

具体的にはこんな下書きをハンドラに入れています。

// app/api/webhook/route.ts (抜粋)
import Stripe from "stripe";
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(req: Request) {
  const payload = await req.text();
  const sig = req.headers.get("stripe-signature")!;
 
  // Cloudflare Workers では constructEventAsync が必須
  const event = await stripe.webhooks.constructEventAsync(
    payload,
    sig,
    process.env.STRIPE_WEBHOOK_SECRET!,
    undefined,
    Stripe.createSubtleCryptoProvider(),
  );
 
  // なぜ最初に重複チェックを入れるか:
  //   メール送信や課金確定のような副作用を、同じ event.id で 2 回起こさないため。
  const dupKey = `webhook:${event.id}`;
  const seen = await env.KV.get(dupKey);
  if (seen) {
    return Response.json({ received: true, duplicate: true });
  }
  await env.KV.put(dupKey, "processed", { expirationTtl: 60 * 60 * 24 * 7 });
 
  // 以降、event.type ごとに処理を分岐
  // ...
  return Response.json({ received: true });
}

Claude Code に「このルートに冪等性チェックを入れて、テストでは同じ event.id で 5 回叩いて 2 回目以降が duplicate=true になることを確認してください」と頼むと、テストコードまで含めて一気に書いてくれるので、自分で検証手順を書くより速く確実に終わります。

「動いた」あとに必ず確認しておくべきこと

ローカルで通ってから本番投入するまでの間に、私は以下を毎回チェックしています。デプロイ後に気づくと夜中の対応コースになりがちな項目ばかりです。

  • 本番用の STRIPE_WEBHOOK_SECRET.env.local の値と混ざっていないか
  • 失敗時のレスポンスが 4xx/5xx を正しく返しているか(無条件 200 を返すと Stripe が再送をやめてしまい、取りこぼしに気づけない)
  • KV / DB の重複防止 TTL が、Stripe の最大再送期間(3 日)より長く設定されているか
  • ハンドラの実行時間が Cloudflare Workers の CPU 時間制限(無料プランで 10 ms、有料プランで 30 s)内に収まっているか

特に 2 つ目の「無条件 200」は、CI/CD で誤って try { ... } catch { return 200 } に書き換わるとサイレント障害になるので、grep -r "return new Response" src/app/api/webhook をコミットフックに入れておくくらいでちょうど良いと感じています。

次にやってみてほしいこと

ローカルで Webhook を安心して触れる環境ができてしまえば、新しい決済フローの追加は急に怖くなくなります。まずは本記事のパターン2 で示した replay-webhook.ts を 1 ファイル作って、過去に失敗した Webhook を 1 件だけ再生してみてください。「触りたくない感」がそれだけで大きく減るはずです。

Stripe 周りの設計をより深く学びたい場合は、関連記事の Next.js + Stripe + VPS で小さな EC ショップを Claude Code と作るガイド や Claude API で Webhook の非同期エラーリカバリを設計する も合わせて読んでみてください。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Claude Code2026-05-06
Claude Code だけで個人 SaaS を 90 日で作って稼ぐ — 設計・実装・収益化の完全記録(2026年版)
個人開発者が Claude Code だけを使い、SaaS プロダクトを 90 日で設計・実装・リリース・収益化した全工程を公開。ツール選定・コスト管理・つまずきポイントまで隠さず記録。
Claude Code2026-04-28
Claude Code で週末MVP開発 — 48時間で作って初売上を立てるまでの全工程
Claude Code を使って週末48時間でMVPを開発し、Stripe決済を組み込んで初売上を立てるまでの全工程を、実際のプロンプトとコマンド付きで解説します。
Claude Code2026-07-17
テーブルが「… 2,847 more rows」で切れていた朝 — 描画上限とトークン計上を切り離して、ツール出力を設計し直す
Claude Code 2.1.209 で 200 行超の markdown テーブルは先頭 200 行+残り件数の表示になりました。省略されるのは描画だけで、モデルは全行を受け取ります。この差を測り、ツール出力を集約型へ組み替える設計をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →