Stripe の Webhook 配信の仕組み

タイムアウトは非公表で、目安は 20 秒です。リトライは 3 日間で、その後エンドポイントは無効化されます。検証済みの署名形式と Node の検証コードを掲載しています。

配信の挙動

プロバイダーの公式ドキュメントから読み取っています(最終確認 2026-08-06)。プロバイダーが数値を示していない項目には、この表も数値を載せていません。

応答期限プロバイダーは未公表

Stripe は数値を公開していない。ドキュメントにあるのは、時間のかかる処理の前に 2xx を返すようにという指示だけ。20 秒はコミュニティでの目安

リトライ本番環境では指数バックオフで最大 3 日間。サンドボックスでは数時間で 3 回試行
打ち切り失敗が続くとエンドポイントが無効化される(その前にメールが届く)。イベントは Events API に 30 日間残る
出典https://docs.stripe.com/webhooks

署名

ドキュメントを読んだだけではなく、テストスイートで裏付けた動作する検証コードで確認しています。最終検証日は 2026-08-20 です。

アルゴリズムHMAC-SHA256
署名対象{timestamp}.{body}
エンコードhex
ヘッダーstripe-signature = t=X,v1=hex
許容範囲5 分

検証する

JSON としてパースする前の生のリクエストボディを、バイト単位でそのまま使います。フレームワークがペイロードを再シリアライズした時点で、このページのどの方式も検証に失敗します。

const crypto = require("node:crypto");

// stripe-signature: "t=1700000000,v1=abc..." — HMAC-SHA256 over "{t}.{body}"
const pairs = Object.fromEntries(
  req.headers["stripe-signature"].split(",").map((p) => p.split("=")),
);
const expected = crypto
  .createHmac("sha256", endpointSecret) // whsec_... from the endpoint's page
  .update(`${pairs.t}.${rawBody}`)
  .digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(pairs.t)) < 300;
const received = Buffer.from(pairs.v1 ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  fresh && received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

SDK に任せる方法もあります:stripe.webhooks.constructEvent(rawBody, sig, secret)。生のリクエストボディが必要なので、Webhook のルートではどの JSON パーサーよりも前に express.raw() をマウントしてください。

落とし穴

Stripe は応答期限を一切公表していません。ドキュメントにあるのは、時間のかかる処理の前に 2xx を返すようにという指示だけです。コミュニティでは 20 秒を目安にしていますが、遅いデータベースに同期的に書き込むハンドラーは、イベントのたびにその限界ぎりぎりになります。

3 日間失敗が続くと、そのイベントは何の通知もなくリトライキューから外れます。失敗が続けばエンドポイントは無効化されます(その前にメールが届きます)。イベントは 30 日間、Events API から取得できます。

あわせて読む

AnyHook は、Stripe から受信するエンドポイントの手前に入ります。約 1 秒で応答し、サーバーがダウンしていれば独自のスケジュールでリトライします。すべてのイベントは再送できる状態で保存されます。 変えるのは URL 1 つだけで、コードはそのままです。ただし Stripe 自身の署名は 5 分で失効するため、リトライと再送を受け付けるには AnyHook-Signature を検証してください。

仕組みを見る →