mcpField
W
webhook-receiver-hardener

webhook-receiver-hardener

github.com/patricio0312revセキュリティ63271 回インストール41 週間前に更新
リポジトリ

webhook-receiver-hardener について

webhook-receiver-hardenerは、安全で信頼性の高いWebhookエンドポイントの構築を支援する開発者向けスキルです。署名検証、重複排除、冪等処理、迅速な応答、運用監視のパターンを示します。

主な機能

  1. 01HMACによる署名検証
  2. 02Redisとデータベース制約による重複排除
  3. 03冪等なWebhook処理の設計
  4. 04迅速な応答と非同期処理
  5. 05エラー応答・監視・インシデント手順の計画

活用シーン

  1. 01サードパーティWebhookエンドポイントの強化
  2. 02重複配信による再処理の防止
  3. 03無効なペイロードや署名へのステータスコード対応
  4. 04Webhook障害対応手順の準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/patricio0312rev/skills --skill webhook-receiver-hardener

インストール後、Claude Code で/webhook-receiver-hardenerを入力して読み込まれたか確認します。

詳細

Webhook Receiver Hardener

失敗を適切に処理する、安全で信頼性の高いWebhookエンドポイントを構築します。

コアセキュリティ

署名検証:処理前にHMACを検証します
重複排除:処理済みのWebhook IDを記録します
冪等性:同じWebhookを複数回安全に処理できます
再試行:プロバイダーによる再試行に対応します
レート制限:不正利用を防ぎます

署名検証

import crypto from "crypto";

export const verifyWebhookSignature = (
  payload: string,
  signature: string,
  secret: string
): boolean => {
  const hmac = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hmac));
};

// Stripe example
router.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const sig = req.headers["stripe-signature"];

    try {
      const event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        process.env.STRIPE_WEBHOOK_SECRET
      );
      await processStripeEvent(event);
      res.json({ received: true });
    } catch (err) {
      return res.status(400).send(`Webhook Error: ${err.message}`);
    }
  }
);

重複排除

// Redis-based dedupe
const WEBHOOK_TTL = 60 * 60 * 24; // 24 hours

export const isDuplicate = async (webhookId: string): Promise<boolean> => {
  const key = `webhook:${webhookId}`;
  const exists = await redis.exists(key);

  if (exists) return true;

  await redis.setex(key, WEBHOOK_TTL, "1");
  return false;
};

// Usage
if (await isDuplicate(webhook.id)) {
  return res.status(200).json({ received: true }); // Already processed
}

冪等な処理

export const processWebhook = async (webhook: Webhook) => {
  // Use database transaction with unique constraint
  try {
    await db.transaction(async (trx) => {
      // Insert webhook record (unique constraint on webhook_id)
      await trx("processed_webhooks").insert({
        webhook_id: webhook.id,
        processed_at: new Date(),
      });

      // Do actual processing
      await performWebhookAction(webhook, trx);
    });
  } catch (err) {
    if (err.code === "23505") {
      // Unique violation
      console.log("Webhook already processed");
      return; // Idempotent - already processed
    }
    throw err;
  }
};

再試行の処理

// Acknowledge immediately, process async
router.post("/webhooks/provider", async (req, res) => {
  // Verify signature
  if (!verifySignature(req.body, req.headers["signature"])) {
    return res.status(401).send("Invalid signature");
  }

  // Return 200 immediately
  res.status(200).json({ received: true });

  // Process async
  processWebhookAsync(req.body).catch((err) => {
    console.error("Webhook processing failed:", err);
    // Will be retried by provider
  });
});

// Exponential backoff for provider retries
// Attempt 1: immediate
// Attempt 2: +5 minutes
// Attempt 3: +15 minutes
// Attempt 4: +1 hour
// Attempt 5: +6 hours

エラーレスポンス

// Return appropriate status codes
const webhookHandler = async (req, res) => {
  // 400: Malformed payload (won't retry)
  if (!isValidPayload(req.body)) {
    return res.status(400).json({ error: "Invalid payload" });
  }

  // 401: Invalid signature (won't retry)
  if (!verifySignature(req.body, req.headers["signature"])) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  // 200: Already processed (idempotent)
  if (await isDuplicate(req.body.id)) {
    return res.status(200).json({ received: true });
  }

  // 500: Processing error (will retry)
  try {
    await processWebhook(req.body);
    return res.status(200).json({ received: true });
  } catch (err) {
    console.error("Processing error:", err);
    return res.status(500).json({ error: "Processing failed" });
  }
};

モニタリングと運用手順書

## Webhook Incidents

### High Error Rate

1. Check provider status page
2. Review recent code deploys
3. Check signature secret rotation
4. Verify database connectivity

### Missing Webhooks

1. Check provider sending (their dashboard)
2. Verify endpoint is accessible
3. Check rate limiting rules
4. Review dedupe cache TTL

### Duplicate Processing

1. Check dedupe cache connectivity
2. Verify unique constraints
3. Review idempotency logic

ベストプラクティス

  • いかなる処理よりも先に署名を検証します
  • 速やかに200を返し、非同期で処理します
  • Redisとデータベースの制約を使って重複を排除します
  • すべてのWebhookの試行をログに記録します
  • 処理のレイテンシーを監視します
  • 失敗を検知するアラートを設定します
  • 想定されるペイロードのスキーマを文書化します

出力チェックリスト

  • 署名検証
  • 重複排除の仕組み
  • 冪等な処理
  • 非同期処理のパターン
  • 適切なステータスコード
  • エラーログ
  • モニタリング/アラート
  • インシデント対応手順書

コメント