「Stripe で売れたら、納品物のリンクをメールで送るだけ。簡単そうなので Claude Code に書かせてみた」。この構成は、動かし始めてしばらくは問題なく見えます。問題が出るのは、Webhook が2回届いた日、購入者が決済直後に回線を切った日、再送されたイベントが順番どおりに来なかった日です。そのとき「納品メールが2通届いた」「払ったのに何も届かない」という連絡が来ます。
この記事では、Stripe の公式ドキュメントで確かめられる仕様だけをもとに、決済完了から納品メール送信までを無人で回すときの設計を整理します。あわせて、Claude Code にコードを書かせるときに、どこまで任せてよく、どこを人が握るべきかを線引きします。読者は、自社のデジタル商品や予約・会員サービスを Stripe Checkout で売っている会社の、業務改善・情報システム・Web の担当者を想定しています。
結論 — 先に要点だけ
- Webhook は必須、決済完了ページは補助。 Stripe は「完了ページだけで納品しない」と明記しています。両方から同じ納品関数を呼び、どちらから呼ばれても一度しか納品しない作りにします
- 届いたリクエストは、署名検証、即時の 2xx 応答、重い処理は後回し、の順で扱う。 署名を確かめずに納品する作りは、偽のリクエストで納品物を取られる穴になります
- 二重送信は3層の記録で防ぐ。 イベント ID、Checkout セッション ID、メール送信済みの記録です。どれか1つでは足りません
- Stripe は順序も一回性も保証しない。 同じイベントが複数回届くこと、生成順と違う順で届くことを前提に組みます。再送は本番で最大3日間続きます
- Claude Code に任せるのはコードとテストまで。 本番の鍵、本番モードへの切り替え、返金や手動再送の操作は人が握ります
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
全体の流れ — 決済完了から納品メールまでの4段階
Stripe Checkout を使った販売では、決済が終わったあとに自社サーバーへ2つの経路で「売れた」という情報が届きます。1つは Stripe から直接届く Webhook、もう1つは購入者がブラウザで戻ってくる決済完了ページです。
| 段階 | 起点 | 自社サーバーで起きること | よくある失敗 |
|---|---|---|---|
| 1. セッション作成 | 購入ボタン | Checkout セッションを作り、Stripe の決済ページへ送る | 完了ページの URL にセッション ID の差し込みを忘れる |
| 2. 決済完了 | 購入者の操作 | Stripe 側で支払いが確定し、checkout.session.completed イベントが発生する | 銀行振込など遅延型の支払い方法を考慮していない |
| 3. 通知の受信 | Webhook と完了ページ | 2つの経路から納品関数が呼ばれる。同時に呼ばれることもある | 片方の経路しか実装していない。排他が無い |
| 4. 納品とメール | 納品関数 | 支払い状態を確認し、納品物を用意し、メールを送り、記録する | 記録の前にメールを送り、途中で落ちて再送される |
Stripe の公式ガイドは、完了ページだけに頼れない理由を「購入者が支払いを済ませたあと、ページが読み込まれる前に回線を失うことがある」と説明し、Webhook を納品の必須経路としています。一方で Webhook は遅れることがあるため、購入者が目の前にいるときにすぐ納品するために完了ページからも納品関数を呼ぶことを推奨しています。 ※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)
完了ページの URL には {CHECKOUT_SESSION_ID} という差し込みを含めます。購入者が戻ってきたときに、この ID で Checkout セッションを取得し、納品関数に渡します。ホスト型の Checkout では、完了ページへ戻す前に Stripe が自社の Webhook 応答を最大10秒待つ、という挙動も公式に書かれています。Webhook の処理が重いと、購入者の画面遷移まで遅くなります。
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)
Checkout セッション自体は、既定では作成から24時間で期限切れになります。期限は30分から24時間の範囲で変えられます。期限切れのイベント(checkout.session.expired)を受けて在庫を戻す、といった使い方も公式に例示されています。
※ 出典: Stripe Docs — How Checkout works(取得 2026-11)
Webhook を受ける前に決める3つのこと
署名を確かめてから中身を見る
Stripe は送るイベントすべてに Stripe-Signature ヘッダーで署名を付けます。公式ドキュメントは、検証しない場合の危険を「攻撃者が偽の Webhook イベントを送り、注文の納品やアカウントへのアクセス付与を引き起こせる」と具体的に書いています。納品の自動化では、この検証が最初の関所です。
検証に使うのは、エンドポイントごとに発行される whsec_ で始まる署名シークレットです。API キーとは別物で、同じ URL でもサンドボックスと本番で値が違います。検証にはリクエストの生のボディが必要で、フレームワークが JSON として解釈し直したあとの文字列では失敗します。Next.js や Express の設定で落ちる典型例は、公式のトラブルシューティングに載っています。
※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)/Stripe Docs — Manage webhook endpoints(取得 2026-11)
| 項目 | 公式ドキュメントの記述(2026年11月時点) |
|---|---|
| 署名の方式 | HMAC-SHA256。ヘッダーに時刻 t= と署名 v1= が入る |
| 時刻の許容幅 | 公式ライブラリの既定は5分。0 にすると再送攻撃の対策が無効になる |
| 生のボディ | 署名検証には加工前のボディが必要 |
| 通信 | 本番は HTTPS 必須。TLS 1.2 と 1.3 のみ |
| 登録できる数 | イベント送信先は最大16件 |
| シークレットの更新 | ダッシュボードから更新できる。旧シークレットの失効を最大24時間遅らせ、その間は両方で署名される |
※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
署名検証に加えて、Stripe の送信元 IP アドレスだけを受け付ける設定も公式に推奨されています。ファイアウォールで制御できる環境なら、二重の守りにしておきます。
2xx をすぐ返し、重い処理は後ろに回す
公式ドキュメントは「複雑な処理の前に、すみやかに 2xx を返す」ことを求めています。例として挙がっているのは「会計システムで請求書を支払い済みにする前に 200 を返す」です。納品物の生成やメール送信を Webhook の応答前に同期で行うと、タイムアウトで失敗扱いになり、再送が始まります。
実装の形としては、Webhook ハンドラは「署名検証、イベント ID の記録、キューへの投入、200 応答」までにとどめ、納品関数は別のワーカーやジョブとして動かします。小さな構成なら、ハンドラ内で記録だけして応答し、納品は非同期の処理に渡す形でも成立します。 ※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
順序と一回性を期待しない
Stripe はイベントが生成順に届くことを保証していません。同じイベントが複数回届くこともあります。公式ドキュメントは、イベントの created を順序や処理済み判定に使わないこと、代わりにイベント ID を記録して重複を見分けることを求めています。
※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
| 挙動 | 公式ドキュメントの記述(2026年11月時点) |
|---|---|
| 自動再送(本番) | 最大3日間、間隔を広げながら再送する |
| 自動再送(サンドボックス) | 数時間のうちに3回 |
| 手動再送 | ダッシュボードからはイベント作成後15日まで、CLI からは30日まで |
| 順序 | 生成順の配送は保証されない |
| 重複 | 同じイベントが複数回届くことがある。別のイベントオブジェクトとして二重に生成される場合もある |
| 再送時の署名 | 再送のたびに新しい署名と時刻が付く |
※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
「別のイベントオブジェクトとして二重に生成される場合」があるため、イベント ID だけで重複を弾くと取りこぼします。公式は data.object の ID と event.type の組で見分けるよう書いています。納品の文脈では、これが次の「セッション ID の記録」に当たります。
二重送信を防ぐ — 冪等化の3層
納品メールが2通届く原因は、ほぼ「同じ仕事が2回走ったのに、2回目を止める記録が無かった」ことです。Stripe の公式ガイドは、納品関数の要件の1つ目に「同じ Checkout セッション ID で複数回、場合によっては同時に呼ばれても正しく動くこと」を置いています。これを満たすには、記録を3層に分けます。
| 層 | 鍵にするもの | 防ぐもの | 記録のタイミング |
|---|---|---|---|
| 1. イベント | イベント ID | 同じイベントの再送による二重処理 | 署名検証の直後、処理を始める前 |
| 2. 注文 | Checkout セッション ID | Webhook と完了ページの両方からの納品、別イベントによる二重納品 | 納品を始める前に「処理中」を書く |
| 3. 送信 | セッション ID とメール種別 | 納品は一度でもメールだけ二重に出る事故 | 送信の直前に「送信中」、直後に「送信済み」 |
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)
第1層 — イベント ID を処理前に記録する
公式ドキュメントが示す基本形は「処理済みのイベント ID を記録し、記録にあるものは処理しない」です。未配送イベントを手動で処理する手順にも、処理中か処理済みなら飛ばす、処理中と記録する、処理する、処理済みと記録する の4段階が載っています。記録先はデータベースの1テーブルで足ります。メモリ上の集合では、サーバーが再起動したり複数台になったりした時点で崩れます。
※ 出典: Stripe Docs — Manage webhook endpoints(取得 2026-11)
第2層 — セッション単位で「処理中」を先に取る
Webhook と完了ページは別のプロセスから、ほぼ同時に納品関数を呼びます。「納品済みか確認してから納品する」という素朴な作りだと、2つのプロセスが同時に「未納品」と読み、両方が納品します。先に「このセッションは処理中」という行を、重複を許さない制約付きで書き込み、書けた側だけが納品に進む形にします。公式ガイドが求める「セッションを取得し、payment_status が unpaid でないことを確かめてから納品し、納品状態を記録する」という流れは、この行を取ったあとに実行します。
payment_status の確認は、遅延型の支払い方法を使うときに効きます。銀行振込のような支払い方法では、Checkout が完了しても入金はあとで確定し、確定時に checkout.session.async_payment_succeeded が届きます。失敗時は checkout.session.async_payment_failed です。完了イベントだけを見て納品すると、入金前に納品物を渡すことになります。
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe Docs — Types of events(取得 2026-11)
第3層 — メール送信も記録してから送る
納品が一度でも、メール送信の部分だけが2回走ることがあります。たとえば、送信 API の応答待ちでプロセスが落ち、再実行されたときです。送信の直前に「送信中」を書き、送信後に「送信済み」とメッセージ ID を書きます。再実行時に「送信中」のまま残っている行があれば、自動では再送せず、人が確認する一覧に載せます。送信しすぎるより、送り漏れを人が拾うほうが、購入者への迷惑が小さいためです。
Stripe への書き込みには冪等キーを付ける
納品関数から Stripe の API へ書き込む処理(たとえば領収書の再発行や顧客情報の更新)があるなら、Idempotency-Key ヘッダーを付けます。接続エラーで再試行しても、同じ操作が二度実行されません。キーは255文字まで、推奨は V4 UUID などの十分にランダムな文字列で、メールアドレスのような個人情報を鍵にしないよう書かれています。API v1 では、24時間以上たったキーは自動で削除され、その後に同じキーを使うと新しいリクエストとして扱われます。
※ 出典: Stripe Docs — Idempotent requests(取得 2026-11)
Claude Code に任せる範囲と、人が握るもの
ここまでの設計を Claude Code に実装させるのは現実的です。公式ドキュメントが具体的で、テスト用の CLI もそろっているためです。ただし、任せてよいのは「コードを書く」「サンドボックスで動かして直す」までで、鍵と本番の操作は人が持ちます。
| 項目 | Claude Code に任せる | 人が握る |
|---|---|---|
| Webhook ハンドラと納品関数のコード | 任せる | レビューして承認する |
| 3層の記録のテーブル設計とテスト | 任せる | 排他の方式を確認する |
| Stripe CLI での動作確認 | 任せる(サンドボックスのみ) | 結果のログを読む |
| 秘密鍵・署名シークレットの値 | 見せない | 環境変数か秘密管理サービスに置く |
| 本番モードへの切り替え | 任せない | ダッシュボードで人が行う |
| 返金・手動再送・鍵の更新 | 任せない | 手順書どおり人が行う |
鍵を Claude Code に見せない設定
Stripe は、秘密鍵をソースコードや設定ファイルに書かないこと、秘密管理サービスか環境変数で渡すこと、権限を絞った制限付きキー(rk_ で始まる)を使うことを求めています。無制限の秘密鍵(sk_)は新しい用途には推奨されていません。本番の鍵は作成時に一度しか表示されず、あとから見直せません。
※ 出典: Stripe Docs — API keys(取得 2026-11)/Stripe Docs — Best practices for managing secret API keys(取得 2026-11)
Claude Code 側では、設定ファイルの permissions.deny で .env の読み取りを拒否します。公式ドキュメントの例をそのまま使えます。
{
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)"
]
}
}
この設定は、チームで共有する .claude/settings.json に置いてリポジトリに入れます。deny の規則は、各メンバーがフォルダを信頼する前から効きます。ただし deny はツールの呼び出しを止めるもので、Bash で cat .env と打たれた場合まで文字どおりに止めるかは規則の書き方に依存します。確実に隠したいなら、別記事「Claude Code に API キーを見せずに使わせる」で扱ったサンドボックスの仕組みを併用します。
※ 出典: Claude Code Docs — Settings files and precedence(取得 2026-11)/Claude Code Docs — Security(取得 2026-11)
Stripe 側には、AI エージェント向けの仕組みもあります。制限付きキーを作るときに「エージェントのアクセスを認可する」用途として作ると、そのキーは承認ルールの対象になり、返金や出金など高リスクの操作は指定した確認者の承認が必要になります。Claude Code や社内のエージェントに Stripe の鍵を持たせる必要が出たときは、この種類のキーを使います。 ※ 出典: Stripe Docs — API keys(取得 2026-11)
Claude Code への指示文の雛形
以下は、3層の記録を含めて実装させるときの指示の骨組みです。自社の言語やフレームワークに合わせて書き換えてください。
Stripe Checkout の決済完了から納品メール送信までを実装してください。
前提:
- 鍵と署名シークレットは環境変数 STRIPE_API_KEY / STRIPE_WEBHOOK_SECRET から読む。値は参照しない。
- サンドボックスのみで動作確認する。本番モードの設定には触らない。
要件:
1. POST /webhook は、生のボディで署名検証 → イベント ID を processed_events に記録 → キューに投入 → 200 を返す。
署名検証に失敗したら 400 を返し、何も処理しない。
2. fulfill_checkout(session_id) は、fulfillments に (session_id) を一意制約付きで「processing」として挿入できた場合だけ進む。
セッションを line_items 展開で取得し、payment_status が unpaid なら何もしない。
3. メール送信は email_sends に (session_id, kind) を「sending」として記録してから送り、送信後に「sent」とメッセージ ID を書く。
4. GET /after-checkout?session_id= からも fulfill_checkout を呼び、結果に応じた画面を返す。
5. checkout.session.completed / async_payment_succeeded / async_payment_failed を扱う。
6. 同時に2回呼ばれても納品とメールが1回になるテストを書く。
出力: 変更ファイル一覧、テスト結果、stripe listen / stripe trigger で確認した手順。
指示文にある stripe listen と stripe trigger は Stripe CLI のコマンドです。stripe listen --forward-to localhost:4242/webhook で、Stripe からのイベントを手元のサーバーへ転送し、その場でサンドボックス用の署名シークレットが表示されます。stripe trigger checkout.session.completed で完了イベントを発生させられます。遅延型の checkout.session.async_payment_succeeded と async_payment_failed も trigger の対応一覧に入っています。
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe CLI — trigger(取得 2026-11)
手順 — サンドボックスで組んで本番に移す8ステップ
- サンドボックスを作り、テスト用の鍵を環境変数に置く。
sk_test_やrk_test_で始まる鍵です。本番の鍵はこの段階では触りません - Claude Code に3層の記録を含む実装を書かせる。 上の雛形を使います。鍵の値は
.envに置き、denyで読み取りを拒否しておきます stripe listenで手元に転送し、stripe triggerで完了イベントを発生させる。 ログにイベント ID、セッション ID、送信済みの記録が残ることを確かめます- 同じイベントを2回送って、納品とメールが1回で止まることを確かめる。 ダッシュボードの「再送」でも、trigger の再実行でも構いません
- テスト用のカード番号で、実際の Checkout 画面から購入する。 公式ガイドには
4242 4242 4242 4242と、有効期限は未来の日付、CVC は任意の3桁、郵便番号は任意、と書かれています。完了ページに戻る経路と Webhook 経路の両方で納品関数が呼ばれ、結果が1回分であることを見ます - Webhook エンドポイントをダッシュボードで登録し、必要なイベント種別だけを選ぶ。 全イベントの購読は公式が非推奨です。署名シークレットは本番用に別の値になります
- 本番モードの鍵と署名シークレットを、人が秘密管理サービスか環境変数に入れる。 鍵は制限付きキーにし、権限を Checkout と納品に必要な範囲に絞ります
- 本番で少額の実購入を1件行い、記録の3層とメールを確認する。 以後は「Claude Code で作った自動化が止まっていないか確かめる」の通知と記録の最小セットに乗せます
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe Docs — API keys(取得 2026-11)/Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
鍵の更新は、ダッシュボードで「ローテーション」を選ぶと、旧キーと新キーが最大7日間併用できる猶予が公式に用意されています。更新の前に旧キーのリクエストログがゼロになったことを確かめてから失効させる、という手順も書かれています。鍵の更新を含む運用手順は、実装と一緒に Claude Code に文書化させておくと、担当が替わっても回ります。 ※ 出典: Stripe Docs — API keys(取得 2026-11)
社内でこの決済から納品までの仕組みを自分たちで組む場合の進め方は、企業向けの有料の実務書にもまとめています。
失敗パターン集 — 5つ
- 完了ページだけで納品している。 購入者が戻ってこない決済は納品されません。Stripe が「Webhook は必須」と書いているのはこのためです
- 署名検証を省いて、イベントの中身だけで納品している。 エンドポイントの URL を知られた時点で、偽のイベントで納品物を取られます。検証は最初に、失敗したら 400 で終わる作りにします
- 「納品済みか確認してから納品」だけで、処理中の記録を取っていない。 Webhook と完了ページが同時に走ると両方が納品します。一意制約付きの「処理中」を先に取ります
- メールを送ってから送信済みを書いている。 送信後に落ちると再実行で2通目が出ます。送信前に「送信中」を書き、残っているものは人に回します
- Webhook の応答前に納品物を生成している。 生成が重いとタイムアウトになり、再送が始まり、再送のたびに生成が走ります。応答と処理を分けます
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
公開前チェックリスト — 印刷して使う12項目
以下はすべて、Stripe と Claude Code の公式ドキュメントに記述がある項目です。 ※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)/Stripe Docs — Fulfill orders(取得 2026-11)
- 完了ページの URL に
{CHECKOUT_SESSION_ID}を含めている - Webhook ハンドラは生のボディで署名を検証し、失敗時は 400 で終わる
- 時刻の許容幅を 0 にしていない(既定の5分のまま、または意図した値)
- 署名検証の直後にイベント ID を記録し、記録済みのイベントは処理せず 200 を返す
- 納品はセッション ID の一意制約付き「処理中」行を取れた場合だけ進む
-
payment_statusがunpaidのセッションには納品しない -
async_payment_succeededとasync_payment_failedを扱っている(遅延型の支払い方法を有効にしている場合) - メール送信は「送信中」を書いてから送り、「送信済み」とメッセージ ID を書く
- Webhook の応答前に、納品物の生成やメール送信をしていない
- 購読するイベント種別を必要なものだけに絞っている
- 鍵と署名シークレットはコードと設定ファイルに書かず、Claude Code の設定で
.envの読み取りを拒否している - 本番の鍵は制限付きキーで、更新手順と担当者が文書になっている
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)/Stripe Docs — Best practices for managing secret API keys(取得 2026-11)
よくある質問
Q. Webhook だけで納品してよいですか。決済完了ページからも納品処理を呼ぶ必要がありますか?
Stripe は Webhook を必須としたうえで、決済完了ページからも同じ納品関数を呼ぶことを推奨しています。購入者が完了ページに戻らない場合があるため Webhook が欠かせず、完了ページは目の前の購入者にすぐ納品するためのものです。両方から、しかも同時に呼ばれる前提で、同じセッション ID では一度しか納品しない作りにします。 ※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)
Q. Webhook が届かなかったとき、Stripe は再送してくれますか?
本番モードでは最大3日間、間隔を広げながら再送します。サンドボックスでは数時間のうちに3回です。再送のたびに新しい署名と時刻が付くので、署名検証をしていれば再送も正しく受け取れます。手動の再送はダッシュボードからはイベント作成後15日まで、CLI からは30日までです。 ※ 出典: Stripe Docs — Receive Stripe events in your webhook endpoint(取得 2026-11)
Q. Claude Code に Stripe の秘密鍵を渡さずに Webhook の処理を書かせることはできますか?
できます。鍵は環境変数か秘密管理サービスに置き、Claude Code の設定で .env の読み取りを拒否します。手元の確認には Stripe CLI がサンドボックス用の署名シークレットを出力するので、本番の鍵を使わずに決済完了から納品までの一連の動きを試せます。
※ 出典: Claude Code Docs — Settings files and precedence(取得 2026-11)/Stripe Docs — Fulfill orders(取得 2026-11)
Q. 納品メールが2通届いたという連絡が購入者から来ました。どこを直せばよいですか?
イベント ID の記録、セッション単位の納品記録、メール送信の記録という3層のどれが抜けているかを順に確かめます。多いのは、Webhook と完了ページの両方から同時に呼ばれたときの排他が無い場合と、送信済みの記録を送信の後に書いていて途中で落ちると再送される場合の2つです。どちらも、記録を「処理の前」に書く形へ直せば止まります。
Q. サブスクリプションでも同じ考え方でよいですか?
基本は同じですが、月次の更新は checkout.session.completed ではなく請求書系のイベント(invoice.paid など)で届きます。Stripe の公式ガイドは、サブスクリプションや遅延型の支払い方法を使う場合は Webhook による自動納品が必須だと明記しています。更新のたびに何を納品するか(アクセス期間の延長か、月次レポートの送付か)を先に決め、同じ3層の記録を請求書 ID で持ちます。
※ 出典: Stripe Docs — Fulfill orders(取得 2026-11)/Stripe Docs — Types of events(取得 2026-11)
次の一歩
まず、今動いている仕組みに対して上のチェックリストを当ててください。12項目のうち、署名検証、処理中の記録、送信前の記録の3つが無い場合は、購入が増える前に直す価値があります。これから作る場合は、サンドボックスで Claude Code に書かせ、同じイベントを2回送るテストが通るまでは本番の鍵を触らない、という順番を守ってください。
自社の商品や既存の仕組みに合わせて、どこまでを自動化し、どこを人が握るかを一緒に整理したい場合は、現状の構成(決済の種類・納品物・使っている言語)を書いてお送りください。書面で実装の手順と確認項目をお返しします。契約前提ではありません。
自動化の費用対効果を先に見積もりたい場合は、AI 自動化 ROI 試算ツールで現状の作業時間から概算できます。
更新履歴
- 2026-11-07: 初版公開