◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SONNET 4.5 — Claude API での提供終了は 11/30、残り27日。API 連携を足したアプリは移行先の確認を●SDK 58 — React Native 0.88 に更新されました。Android のリリース版で R8 が既定オンになり、iOS は SceneDelegate を生成します●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SONNET 4.5 — Claude API での提供終了は 11/30、残り27日。API 連携を足したアプリは移行先の確認を●SDK 58 — React Native 0.88 に更新されました。Android のリリース版で R8 が既定オンになり、iOS は SceneDelegate を生成します●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-06-16上級

Rork アプリの Stripe Webhook が本番でだけ取りこぼす — 冪等化・再送・順序ずれに耐える受信設計の実装メモ

ローカルでは通るのに本番でだけ権限付与がずれる Stripe Webhook を、署名検証・冪等化・順序ずれ補正・再送ストーム対策の4層で安定させた実装メモです。Cloudflare Workers と KV を前提にした受信設計を、運用で踏んだ落とし穴ごとに整理します。

Stripe20webhookCloudflare Workers26冪等性3決済8

✦ プレミアム記事

決済は成功しているのに、ユーザーの権限が変わらない。Stripe の管理画面では Payment succeeded、Recent deliveries も 200 OK。なのにアプリ側は無料プランのまま——。個人開発で課金まわりを触っていると、この「全部正しく見えるのに合わない」状態に何度かぶつかります。

厄介なのは、ローカルでは stripe trigger で一発で通るのに、本番でだけ静かに取りこぼすことです。署名検証のような分かりやすいエラーは早めに気づけます。本当に時間を溶かすのは、検証は通っているのに「同じイベントを二回処理した」「イベントが順番どおりに来なかった」「処理が遅くて Stripe に再送された」といった、ログを見ても一見正常に見える種類の失敗でした。

ここでは Rork で作ったアプリのバックエンド(Cloudflare Workers + KV)を題材に、Webhook 受信を安定させるために実際に入れた4つの層を、踏んだ落とし穴とセットで残しておきます。署名検証の話は最小限にして、その先の「受け取った後どう壊れるか」に重心を置きます。

なぜ「200 が返っているのに合わない」が起きるのか

最初に押さえておきたいのは、Stripe の Webhook が at-least-once(少なくとも一回)配信だという前提です。これは「同じイベントが二回以上届くことがある」という意味です。ネットワークの揺らぎ、こちらのレスポンスが遅い、Stripe 側のリトライ——理由は色々ありますが、結果として checkout.session.completed が二回飛んでくる状況は、低頻度ながら確実に起きます。

受信ハンドラが「届いたイベントを毎回そのまま処理する」前提で書かれていると、二回目の配信でもう一度権限を付与したり、サブスク開始処理を重ねて走らせたりします。冪等化していなければ、これは正常系のログ(200 OK が二行)として記録されるだけで、エラーには見えません。「全部正しく見えるのに合わない」の正体の多くがこれです。

もうひとつ、サブスクリプションのライフサイクルイベントは 順序が保証されません。customer.subscription.created より先に customer.subscription.updated が届くことがあります。payload の中身を時系列の真実として信じて状態機械を回すと、古い情報で上書きしてしまいます。

つまり安定化の本質は、署名検証そのものよりも「重複に強い」「順序ずれに強い」「遅延に強い」受信設計をどう作るかにあります。

第1層: Workers 上での署名検証を非同期版で通す

土台として署名検証は必要です。ここだけは Cloudflare Workers 固有のはまりどころがあるので最小限に触れておきます。Node 向けでよく使う同期版 constructEvent() は Workers の Web Crypto 環境では動きません。非同期版に切り替え、ボディは必ず生テキストで受け取ります。

// src/app/api/webhook/route.ts
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
 
export async function POST(req: NextRequest) {
  // json() ではなく text()。パースするとバイト列が変わり署名検証が必ず落ちる
  const body = await req.text();
  const signature = req.headers.get('stripe-signature') ?? '';
 
  let event: Stripe.Event;
  try {
    const cryptoProvider = Stripe.createSubtleCryptoProvider();
    event = await stripe.webhooks.constructEventAsync(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
      undefined,
      cryptoProvider,
    );
  } catch (err) {
    // 署名が合わない=こちらの設定ミス。400 を返すと Stripe は再送しない
    console.error('signature verification failed:', err);
    return NextResponse.json({ error: 'invalid signature' }, { status: 400 });
  }
 
  return handleEvent(event);
}

ここで覚えておきたいのは、署名検証で落としたときに 400 を返す ことです。署名不一致は通信障害ではなく設定の問題なので、再送させても直りません。逆に、後述する一時的な処理失敗では 500 を返して再送させます。この「いつ再送させ、いつ諦めさせるか」の区別が、再送ストーム対策の出発点になります。

✦

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦イベントIDを使った KV 冪等化で、Stripe の at-least-once 再送による二重付与を止める具体的な実装
✦署名検証の通過後に「速く 2xx を返してから処理する」設計へ切り替え、再送ストームを未然に防ぐ手順
✦順序ずれするサブスクリプションイベントを payload で信じず、retrieve で現在状態に寄せて補正する運用パターン
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

⬡ 開発ツール2026-04-26
Rork × Stripe Meter で AI アプリの使用量課金を実装する — トークン数・API 呼び出し回数で正確に課金する設計ガイド
Stripe Meter Billing で Rork アプリに使用量課金を実装します。メーター報告・冪等キー・リトライキュー・イベント集約・前払いクレジットとの使い分けまで、運用の実感を交えて解説します。
⬡ 開発ツール2026-10-20
デプロイ直後に出る Application error は、なぜ再デプロイでは直らないのか
シークレットを差し替えた直後から、どのページを開いても Application error。原因は、古い HTML が、もう置かれていない JS を呼んでいたことでした。検知スクリプト、エッジキャッシュの版照合、1回だけの自動再読み込みを、動かして確かめた形で残します。
⬡ 開発ツール2026-05-09
Rork の A/B テスト基盤を GrowthBook と PostHog でゼロから組む
Firebase に頼らず、GrowthBook と PostHog をオープンソースで組み合わせて Rork アプリの A/B テスト基盤を構築する手順を、SDK 統合からベイズ統計の解釈、運用上の落とし穴までまとめました。