Slack の Webhook 配信の仕組み

Slack は 3 秒以内の 2xx を求め、リトライは 3 回、1 時間に試行の 95% 超が失敗するとイベントサブスクリプションを無効化します。署名形式と Node 検証コードを載せています。

配信の挙動

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

応答期限3 秒

ハンドラーが期限内に応答するか確認する

リトライ3 回リトライ:即時、1 分後、5 分後
打ち切り60 分間で配信試行の 95% 超が失敗すると、イベントサブスクリプションが無効になる
出典https://docs.slack.dev/apis/events-api/

署名

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

アルゴリズムHMAC-SHA256
署名対象v0:{timestamp}:{body}
エンコードhex
ヘッダーx-slack-signature = v0=hex
許容範囲5 分

検証する

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

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

const ts = req.headers["x-slack-request-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error("stale");

const expected =
  "v0=" +
  crypto
    .createHmac("sha256", signingSecret) // Slack App → Basic Information
    .update(`v0:${ts}:${rawBody}`)
    .digest("hex");
const received = Buffer.from(req.headers["x-slack-signature"] ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

落とし穴

3 秒は主要な送信元の中で最も短い応答期限で、3 回のリトライも 5 分以内に終わります。ダウンではなく遅いだけのハンドラーでも、1 回の障害の間に 4 回の試行すべてに失敗します。

無効化の条件は回数ではなく割合です。60 分以内に配信試行の 95% 超が失敗すると、アプリ全体のイベントサブスクリプションがオフになります。その間、ユーザーはチャンネルでボットが黙り込むのを目にします。

あわせて読む

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

仕組みを見る →