mcpField
W
webhook-receiver-hardener

webhook-receiver-hardener

github.com/patricio0312rev安全63271 次安装41 周前更新
代码仓库

关于 webhook-receiver-hardener

webhook-receiver-hardener 是帮助开发者构建安全可靠 Webhook 接收端点的技能。它围绕签名校验、去重、幂等处理、快速确认与异步处理提供实现模式和运维建议。

主要功能

  1. 01使用 HMAC 校验签名
  2. 02通过 Redis 和数据库约束去重
  3. 03设计幂等的 Webhook 处理
  4. 04快速确认并异步处理请求
  5. 05规划错误响应、监控和事件手册

适用场景

  1. 01加固第三方 Webhook 接收端点
  2. 02防止重复投递导致重复处理
  3. 03按状态码处理无效请求与签名
  4. 04制定 Webhook 故障排查流程

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/webhook-receiver-hardener确认已加载。

详细介绍

Webhook 接收器加固

构建安全、可靠且能优雅处理故障的 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