Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Why Stripe Webhook Signature Verification Fails on Replays (and How to Fix It)

2026年09月30日に「Dev.to」が公開したITニュース「Why Stripe Webhook Signature Verification Fails on Replays (and How to Fix It)」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

StripeのWebhookは5分以内に処理しないと署名検証に失敗し、再送時にエラーとなる。安易な対処は危険で、プロキシが受信時に検証し、アプリ転送時に新しい署名で再署名する方式が安全な解決策だ。アプリはコード変更なく安全に処理でき、HookArmorで実現できる。

ITニュース解説

Stripeのような決済サービスを利用する際、ユーザーの行動(サブスクリプションの更新や支払い完了など)に応じて、開発者のアプリケーションにリアルタイムで通知する仕組みがWebhookである。システムエンジニアを目指す初心者にとって、このWebhookは外部サービスと連携する上で非常に重要な技術基盤となる。

Stripeから送られるWebhookリクエストは、その信頼性と改ざん防止のために「署名検証」というセキュリティメカニズムを備えている。具体的には、HTTPリクエストのヘッダーに「Stripe-Signature」という項目が付与される。このヘッダーの中には、t=で始まるUnixタイムスタンプと、v1=で始まるHMAC-SHA256というアルゴリズムで計算されたハッシュ値が含まれている。このv1のハッシュ値は、Stripe側でアプリケーションの秘密鍵と、Webhookのタイムスタンプ、そして未解析の生のJSON形式のペイロード(イベントの内容)を組み合わせて生成されたものである。

アプリケーション側がStripe SDKのstripe.webhooks.constructEventのような関数を呼び出して署名を検証すると、SDKは二つの独立したチェックを実行する。一つ目は「暗号学的真正性」のチェックだ。これは、受け取ったタイムスタンプと生のJSONペイロード、そしてアプリケーションがStripeから事前に設定した秘密鍵を使って、SDKが独自にハッシュ値を計算し、Stripe-Signatureヘッダー内のv1の値と一致するかを確認する。一致すれば、リクエストはStripeから正しく送られ、途中で内容が変更されていないと判断できる。二つ目は「リプレイ保護」のチェックである。これは、ヘッダーのタイムスタンプが、現在の時刻から300秒(5分)以内であるかどうかを検証する。この5分という時間制限は、古いリクエストが繰り返し送信される「リプレイ攻撃」を防ぐための重要なセキュリティ機能である。もしタイムスタンプが5分を超えていた場合、SDKはそのリクエストを不正なものとして拒否する。

この「リプレイ保護」の仕組みが、Dead-Letter Queue (DLQ) を用いたシステム設計において問題を引き起こすことがある。DLQとは、一時的なエラー(例えばアプリケーションのバグやデータベース接続の問題)によってすぐに処理できなかったメッセージを一時的に保管し、後で再処理するための仕組みである。もしStripeからのWebhookイベントが、アプリケーション側の問題で即座に処理できずDLQに送られ、そこで5分以上経過してから再処理(リプレイ)された場合、Stripe SDKの署名検証は「タイムスタンプが許容範囲外」というエラーを返してしまう。SDKにとっては、DLQからの正当なリプレイも、悪意のあるリプレイ攻撃も、どちらも「5分以上前のタイムスタンプを持つ古いリクエスト」として区別がつかないため、一律に拒否してしまうのだ。

この問題に対して、一般的な誤った対応策がいくつか存在するが、これらは深刻なセキュリティ上の脆弱性を生むため避けるべきである。一つ目の方法は、リトライされたイベントに対して署名検証自体を無効にすることだ。例えば、リトライ時にx-replayed: trueのようなカスタムヘッダーを付与し、このヘッダーがあればSDKの署名検証をスキップするというものだ。しかし、この方法は重大なセキュリティホールとなる。攻撃者がWebhookのURLを知り、偽のイベントペイロードにx-replayed: trueヘッダーを付けて送信した場合、アプリケーションは検証を迂回してその偽イベントを処理してしまう可能性がある。これにより、攻撃者はStripeを介さずに不正な操作を行うことができてしまう。二つ目の方法は、SDKのタイムスタンプ許容範囲を大幅に広げることだ。例えば、300秒を72時間(約3日間)などに設定変更するというものだ。これによりDLQからのリプレイは通るようになるが、これはシステム全体のリプレイ保護を完全に無効化する行為である。過去に傍受された古いリクエストが、何時間、何日も後に再び送信されても受け入れられてしまうため、非常に危険な状態に陥る。

これらの問題を解決するための正しいアーキテクチャは、「Ingress Verification (入り口での検証)」と「Outbound Re-Signing (出口での再署名)」を組み合わせる方法だ。このアプローチでは、StripeからのWebhookリクエストを直接アプリケーションに届けるのではなく、まず「Ingress Proxy(イングレスプロキシ)」と呼ばれる専用の中間層で受け止める。

このIngress Proxyは、StripeからWebhookリクエストを受け取ると、直ちにStripeからのオリジナルの署名が300秒の許容範囲内で有効であるかを検証する。ここで不正なリクエストであれば即座に拒否し、StripeにはHTTP 200 OKレスポンスを返すことで、Stripeがリクエストのタイムアウトを心配して再送処理を続けるのを防ぐ。署名が有効であった場合、Ingress Proxyは、受け取ったイベントのペイロードと関連するメタデータを、SQLiteのような永続的なローカルストレージに安全に保存する。

その後、イベントがアプリケーションに転送される時(これは数ミリ秒後かもしれないし、DLQからのリプレイで数日後かもしれない)、Ingress Proxy内の「Outbound Dispatcher(アウトバウンドディスパッチャー)」というコンポーネントが、永続ストレージからイベントを取り出す。このディスパッチャーは、現在の時刻を基に新しいタイムスタンプを生成し、その新しいタイムスタンプと、保存しておいたオリジナルの生JSONペイロード、そしてアプリケーションの秘密鍵を使って、新しいHMAC署名を計算し直す。そして、この新しいタイムスタンプと新しい署名が記述されたStripe-Signatureヘッダーを付与して、アプリケーションにWebhookリクエストを転送する。

この仕組みにより、アプリケーションは常に「現在の時刻から5分以内という有効なタイムスタンプと、それに紐づく有効な署名」を持つWebhookリクエストを受け取ることになる。結果として、アプリケーションは特別なコード変更をすることなく、Stripe SDKのstripe.webhooks.constructEvent関数をそのまま利用して、初回配信されたイベントも、DLQから遅れてリプレイされたイベントも、全く同じように正しく署名検証を通過させ、処理することができる。

ただし、遅延したイベントをリプレイする際には、古いデータが既に新しいデータで更新されているデータベースの状態を上書きしてしまうリスクが存在する。例えば、過去の「サブスクリプション削除」イベントが、最近の「サブスクリプション作成」イベントの後に処理され、意図せず新しいサブスクリプション情報を消してしまうようなケースだ。これを防ぐため、Ingress Proxyはイベントを保存し、転送する際に、元のタイムスタンプやリプレイであるかどうかの情報など、「プロビナンス(由来)メタデータ」をカスタムヘッダーとして付与すべきである。例えば、x-hookarmor-original-timestampのようなヘッダーに元のタイムスタンプを含める。アプリケーション側では、イベントを処理する前に、このカスタムヘッダーから得た元のタイムスタンプと、データベースに記録されているデータの更新時刻などを比較し、古い情報で新しい情報を上書きしないようなロジックを追加することで、データの整合性を維持できる。

この「Ingress Verify & Outbound Re-Signing」というアプローチは、セキュリティを損なうことなく、DLQからの遅延したWebhookイベントの処理を可能にする、堅牢で信頼性の高い方法である。アプリケーションは、Stripe SDKの標準的な検証ロジックをそのまま利用できるため、Webhook処理のコードがシンプルに保たれる。このパターンは「HookArmor」というオープンソースのリバースプロキシとして具体的に実装されており、Dockerなどのコンテナ技術を用いてアプリケーションの前に配置することで、即時応答、自動リトライ、DLQからの手動リプレイ、そして透過的なStripe再署名といった機能を容易に利用できるようになっている。このアーキテクチャを採用することで、システムエンジニアはWebhookイベント処理の堅牢性と信頼性を大幅に向上させることが可能となる。

関連コンテンツ

関連IT用語

関連ITニュース