webhook-receiver-hardener について
webhook-receiver-hardenerは、安全で信頼性の高いWebhookエンドポイントの構築を支援する開発者向けスキルです。署名検証、重複排除、冪等処理、迅速な応答、運用監視のパターンを示します。
主な機能
- 01HMACによる署名検証
- 02Redisとデータベース制約による重複排除
- 03冪等なWebhook処理の設計
- 04迅速な応答と非同期処理
- 05エラー応答・監視・インシデント手順の計画
活用シーン
- 01サードパーティWebhookエンドポイントの強化
- 02重複配信による再処理の防止
- 03無効なペイロードや署名へのステータスコード対応
- 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の試行をログに記録します
- 処理のレイテンシーを監視します
- 失敗を検知するアラートを設定します
- 想定されるペイロードのスキーマを文書化します
出力チェックリスト
- 署名検証
- 重複排除の仕組み
- 冪等な処理
- 非同期処理のパターン
- 適切なステータスコード
- エラーログ
- モニタリング/アラート
- インシデント対応手順書
コメント
Markdown 対応