DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Claude CodeでWebhook配信保証を設計する:at-least-once配信・HMAC署名・リトライキュー

はじめに

「Webhookを送ったのか送っていないのかわからない」——at-least-once配信・HMAC署名・指数バックオフリトライでWebhook配信を確実にする設計をClaude Codeに生成させる。


CLAUDE.mdに設計ルールを書く

## Webhook配信保証設計ルール

- at-least-once配信(最低1回は必ず届ける)
- HMAC-SHA256でペイロードに署名(ユーザーごとに異なる秘密鍵)
- X-Webhook-Signatureヘッダー: sha256=hex
- タイムスタンプ検証: 5分以上古いリクエストは拒否(リプレイ攻撃防止)
- 配信タイムアウト: 10秒
- リトライ: 1m→5m→15m→1h→4h→12h(6回、合計約17時間)
- 6回失敗でオーナーへアラート
Enter fullscreen mode Exit fullscreen mode

生成される実装(抜粋)

// HMAC署名(タイムスタンプを含めてリプレイ攻撃を防止)
export function signWebhookPayload(payload, secret, timestamp) {
  const signedPayload = timestamp + '.' + payload;
  const signature = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');
  return { signature: 'sha256=' + signature };
}

// 署名検証(受信側)
export function verifyWebhookSignature(payload, timestamp, signature, secret) {
  const ts = parseInt(timestamp, 10);
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false; // 5分以上古い

  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(timestamp + '.' + payload).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// 配信(タイムアウト10秒 + 失敗時リトライスケジュール)
async deliver(deliveryId) {
  const timestamp = Math.floor(Date.now() / 1000);
  const { signature } = signWebhookPayload(delivery.payload, delivery.endpoint.secret, timestamp);

  const response = await fetch(delivery.endpoint.url, {
    method: 'POST',
    headers: {
      'X-Webhook-Signature': signature,
      'X-Webhook-Timestamp': timestamp.toString(),
      'X-Webhook-Delivery': delivery.id,
    },
    body: delivery.payload,
    signal: AbortSignal.timeout(10_000),
  });

  if (!response.ok) {
    const delayMs = RETRY_DELAYS_MS[delivery.attempt]; // 1m→5m→15m→1h→4h→12h
    await deliveryQueue.add('deliver', { deliveryId }, { delay: delayMs });
  }
}
Enter fullscreen mode Exit fullscreen mode

まとめ

  1. CLAUDE.md にat-least-once配信・HMAC-SHA256署名・5分タイムスタンプ許容・6回リトライ(最大17時間)を明記
  2. 署名ペイロードにタイムスタンプを含める ——同じpayloadで古い署名を再利用するリプレイ攻撃を防止
  3. タイムアウト10秒 で受信側の重い処理をブロックしない——応答だけ確認し実際の処理は受信側が非同期で行う
  4. 6回失敗で配信失敗確定+オーナーアラート ——サイレント消失させず、受信エンドポイントの障害を検出してユーザーに通知

セキュリティ設計のレビューは **Security Pack(¥1,480)* の /security-check で確認できます。*

prompt-works.jp

みょうが (@myougatheaxo) — ウーパールーパーのVTuber。

Top comments (0)