实用 webhook 例子:安全实施指南

实用webhook示例:安全实施指南

Find a complete web hook example with code for Node.js, Python, and Go. Learn to securely verify signatures, prevent replay attacks, and debug your endpoints.

马丁·多纳迪厄

马丁·多纳迪厄

内容营销

实用Web钩子示例:安全实施指南

您有一个需要在发生其他地方事件时反应的服务。支付清算。客户记录发生变化。仓库接收推送。您可以每分钟轮询一个API并浪费循环询问“有新内容吗?”,或您可以让源系统在事件发生时调用您。

大多数Web钩子示例文章在这里停止。它们显示一个路由,打印JSON体,返回, 200并将其视为完成。这种版本在有人发送伪造请求,重放有效请求或您的处理程序由于框架在签名验证之前解析了体时就会停止工作。

本指南采用您将在生产中使用的路径。示例足够小以供复制,但它们包括重要部分:原始体处理,HMAC验证,时间戳检查,快速确认和实用调试。

目录

什么是 Webhook 和为什么使用它们

您的账单提供商在 02:13 将发票标记为已付款。如果您的应用程序在 02:14 学习到这一点,客户立即获得访问权限。如果您的应用程序在下一个轮询周期学习到这一点,他们会等待,支持团队会收到一个票,日志中会充满可避免的噪音。Webhook 解决了这个时间问题,通过发送 HTTP 回调来发送事件。

在实际应用中,webhook 是一个事件驱动的 POST 请求,从一个系统发送到另一个系统。提供商检测到一个变化,例如 invoice.paid, order.created,或 push,并将事件数据发送到您控制的 URL。这样可以消除轮询创建的“是否有新内容?”循环,并减少了大量的浪费请求。

This pattern shows up in real systems because it maps cleanly to business events. Stripe posts payment outcomes. GitHub posts repository activity. Shopify posts order updates. The shape is simple, but production behavior is not. A webhook that updates money, access, or inventory deserves the same care as any public API endpoint, especially once retries, duplicates, and untrusted traffic enter the picture.

帮助建立正确的认知模型

一个有用的方法是将 webhook 流程视为四个部分共同工作:

  • 源系统检测事件的服务
  • 目的端点. 你的 HTTP 路由接收它。
  • Event. 发生了一个命名的变化,例如 invoice.paidpush.
  • Payload. 请求体包含了你的 code 所需的详细信息。

提供者发送关于已经发生的事情的信息。你的任务是验证发送者,确认请求是最新的,并在确认后应用更改。最后一部分比许多基本教程承认的更重要。在生产环境中,重复传递是正常行为,而不是边缘案例。

实用规则: 使用 Webhook 进行事件驱动的更新。使用轮询进行预定读取、补充数据或不提供出站事件的提供者。

对于构建更广泛的 工作流自动化和数据集成的团队,Webhook 通常成为保持系统在同步状态而不产生多余请求流量的事件层。如果你在集成密集的服务上工作,Capgo’s 后端开发文章 有用的上下文是因为核心问题会在重试、队列、可观察性和故障处理中出现。

生产环境中什么有效什么无效

通常稳定的设置都是设计得很平淡的。只订阅你需要的事件。将端点限制在提供商或事件家族中。存储事件 ID 以避免重复的副作用。返回一个快速的 2xx 响应一旦请求被验证并排队,然后异步执行更慢的业务逻辑。

易碎的版本很容易识别。一个通用的端点处理所有事情。签名检查在早期测试中被跳过并且从未回来。处理程序直接写入关键表格之前检查事件是否合法或过期。这个在演示中工作,但在重试风暴、提供商故障或攻击者重放旧请求时就会失败。

这个权衡定义了本指南的其余部分。一个 webhook 接收器的“hello world”版本很小。生产就绪的版本从一开始就添加了签名验证、重放防御、重复处理和调试钩子。

Webhook HTTP 请求的解剖学

在编写 code 之前,帮助看看请求作为原始 HTTP 而不是作为框架对象。一个典型的 webhook 只是一个 HTTP POST 请求到一个公共端点,带有头部和 JSON 体。

一个简单的原始请求

POST /webhooks/orders HTTP/1.1
Host: your-app.example
Content-Type: application/json
User-Agent: Provider-Webhooks/1.0
X-Webhook-Signature: sha256=abc123example
X-Webhook-Timestamp: 1712345678

{
  "event": "order.created",
  "id": "evt_123",
  "data": {
    "order_id": "ord_456",
    "status": "created"
  }
}

重要的部分很明显:

  • 方法在实践中,webhook 交付通常是 POST 请求。
  • Content-Type. 最新版本的供应商通常发送 JSON。
  • User-Agent. 对调试有帮助,但从来不够以建立信任。
  • Signature header. 携带供应商的真实性检查。
  • Timestamp header. 用于拒绝过时或重放的请求。

为什么身体形状很重要

您的 code 通常不关心每个字段。它关心事件类型、事件标识符和业务对象。 data. 这就是为什么好的处理程序只解析它们需要的内容并将其余部分用于故障排除的原因。

OpenAPI 现在直接建模了这个模式。 OpenAPI 3.1.0 添加了顶级的 Webhook 支持,具有首类 webhook 支持 webhooks 对象,描述每个 webhook 的方式类似于 Path Item,但由提供者触发。canonical 示例使用一个 newPet webhook,一个 post 操作,一个 JSON 请求体和一个 200 响应来表示收到,正如在 OpenAPI webhook 示例中.

如果您正在为自己的接收器或提供者协议编写文档,强大的示例比抽象的模式文本更有用。 SheetMergy’s API doc examples SheetMergy 的 __CAPGO_KEEP_0__ 文档示例

因为它们让请求示例、字段描述和预期响应如何协调变得明显。

webhook 在传输层上很简单。大多数故障来自于对头部、体编码或签名规则的不匹配假设。

如何安全地验证 webhook 签名

签名的 webhook 答了一个问题:这个载荷来自于知道共享密钥的人吗?

确保请求的真实性和安全性,以下是验证Webhook签名的六步流程图。

验证流程

通常的HMAC流程如下:

  1. 从提供者的头信息中读取签名。
  2. 读取 原始请求正文 准确地像接收到的那样。
  3. 从安全配置中读取Webhook密钥。
  4. 使用相同的算法重新计算预期的HMAC。
  5. 使用安全比较方法比较接收到的签名和计算的签名。
  6. 如果不匹配,则拒绝请求。

那一步骤是原始正文步骤,很多好的实现都在这里失败。如果您的框架首先解析JSON,重新格式化空格或更改编码详细信息,然后再进行散列,计算的签名就不会与提供者的匹配。

注意在真实的code中

我经常看到的错误有:

  • 对已解析的JSON进行哈希不要这样做 JSON.stringify(req.body) 也不要期望它能匹配。
  • 使用普通的字符串等值使用安全的时间比较。
  • 在代码中硬编码密钥将它们保存在环境变量或密钥管理器中。
  • 仅仅依赖头部如果您没有验证签名头部,那么它就没有意义。

对于正在加强服务间密钥管理的团队,Capgo的指南 API key security for app store compliance 因为这里的约束与之相同。密钥轮换、权限范围访问以及避免日志泄露对于 webhook 接收者来说也很重要。

通用验证示例

const crypto = require('crypto');

function verifySignature(rawBody, receivedSignature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  const a = Buffer.from(receivedSignature, 'utf8');
  const b = Buffer.from(expected, 'utf8');

  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

这是故意的通用示例。实际提供商通常在签名前缀、将时间戳合并到签名内容中或以不同的方式编码摘要。规则仍然相同。遵循提供商的exact签名格式,并始终验证原始载荷。

防止重放攻击

即使签名的 webhook 也可能危险,如果它几个小时后到达,并且您的处理程序将其视为新事件。这种情况比团队预期的要常见。代理服务器记录流量、请求负载泄露到错误位置,或者提供商在网络故障后重试,并且您的端点处理相同事件两次。

安全措施检查表,展示了防止 web 应用程序重放攻击的五个关键措施。

签名验证回答一个问题:发送者是否使用共享密钥创建了此载荷?重放保护回答一个不同的问题:是否应该接受此请求?生产接收者需要两者。

实际上最重要的检查

实用的重放防御从一个签名的时间戳开始。提供商在头部或签名消息中包含时间戳,并且您的接收者拒绝请求,如果它们超出了一个小容差窗口。

该流程应如下所示:

  • 从提供商定义的位置读取时间戳. 不要猜测标题名称。
  • 解析它为整数或 RFC 格式的日期, 根据提供商的规范
  • 与您的服务器时间进行比较.
  • 拒绝请求太旧或太遥远.
  • 在支持时签名方案中验证时间戳 当提供商支持时

最后一点很重要。如果时间戳不受签名覆盖,攻击者可以将新时间戳插入并重新发送原始主体。 我总是检查提供商的具体签名格式之前,我才会信任时间戳逻辑。

选择容忍窗口的方法

五分钟是一个常见的默认值。它足够短以缩小攻击窗口,但足够长以承受小时钟漂移和正常网络延迟

这里有一个权衡。30 秒的窗口听起来更安全,但在实际系统中会更频繁地出现问题,尤其是在重试、排队或区域延迟的情况下。30 分钟的窗口更容易操作,但如果签名请求被暴露给攻击者,攻击者将有更多的时间。从几分钟开始,同步您的服务器使用 NTP,然后只在提供商的交付模式支持时才收紧。

重放防御不仅仅是时间戳检查

时间戳验证阻止过期请求。它不会停止重复处理在有效窗口内的请求。如果在该窗口内将同一已签名事件重复发送两次,应用程序仍需要识别它。

使用第二层:

  • 在Redis等短暂存储中跟踪事件ID或交付ID。 将处理程序视为幂等的
  • 以便重复交付不会创建重复订单、电子邮件或计费操作。 记录被拒绝的过期请求
  • 以原因代码记录,但永远不要记录敏感信息或完整的敏感负载。 在验证和排队繁重工作之后
  • 快速返回响应。 已经考虑过过期窗口和撤销的团队会认识到模式。__CAPGO_KEEP_0__的指南

在Capgo应用程序中使用token撤销模式 Capacitor 涵盖相同的操作概念。 一旦有效的凭证或请求不应永远信任。

签名过期仍然不安全。

在 Node.js 中构建 Webhook 接收器

使用 Node 和 Express 还是可以快速在线部署一个严肃的接收器,但有一个陷阱比其他任何一个更重要。您需要访问原始主体,而不是 Express 将其转换为对象之前。

一台笔记本电脑,放在一张木桌上,显示 Node.js 接收器 code 在 VS Code 编辑器环境中。

一个生产环境的 Express 示例

const express = require('express');
const crypto = require('crypto');

const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Capture raw body for signature verification
app.use(
  express.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);

function safeEqual(a, b) {
  const aBuf = Buffer.from(a, 'utf8');
  const bBuf = Buffer.from(b, 'utf8');
  if (aBuf.length !== bBuf.length) return false;
  return crypto.timingSafeEqual(aBuf, bBuf);
}

function verifySignature(rawBody, secret, receivedSignature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return safeEqual(expected, receivedSignature);
}

function isFresh(timestampHeader, toleranceSeconds = 300) {
  const timestamp = Number(timestampHeader);
  if (!Number.isFinite(timestamp)) return false;

  const now = Math.floor(Date.now() / 1000);
  return Math.abs(now - timestamp) <= toleranceSeconds;
}

app.post('/webhooks/example', async (req, res) => {
  const signature = req.get('x-webhook-signature');
  const timestamp = req.get('x-webhook-timestamp');

  if (!WEBHOOK_SECRET) {
    return res.status(500).send('Webhook secret is not configured');
  }

  if (!signature || !timestamp) {
    return res.status(400).send('Missing required security headers');
  }

  if (!isFresh(timestamp)) {
    return res.status(401).send('Stale webhook');
  }

  const valid = verifySignature(req.rawBody, WEBHOOK_SECRET, signature);
  if (!valid) {
    return res.status(401).send('Invalid signature');
  }

  // Acknowledge quickly
  res.status(200).send('OK');

  // Process after acknowledgement
  try {
    const event = req.body;
    console.log('Accepted event:', event.event, event.id);
    // enqueueJob(event)
  } catch (err) {
    console.error('Post-ack processing failed:', err);
  }
});

app.listen(PORT, () => {
  console.log(`Webhook receiver listening on ${PORT}`);
});

为什么这个结构仍然有效

这里有一些选择是故意的:

  • 原始主体捕获发生在中间件中这保留了原始字节以进行哈希。
  • 时间戳在业务逻辑之前进行检查无需为过期流量做工作。
  • The route returns quickly. Long-running work belongs in a queue or background task. 200 快速返回。长时间运行的工作应该放在队列或后台任务中。Post-ack processing is isolated. Even if downstream logic fails, the receiver path stays small.
  • 确认后处理是隔离的。即使下游逻辑失败,接收者路径也保持小。Secrets are the weak point in a lot of webhook implementations. Don’t keep them in source, don’t paste them into test fixtures, and don’t echo them in logs.

Secrets are the weak point in a lot of webhook implementations. Don’t keep them in source, don’t paste them into test fixtures, and don’t echo them in logs. If you need a broader process around rotation and CI handling, Capgo’s guide to If you need a broader process around rotation and CI handling, __CAPGO_KEEP_0__’s guide to managing secrets in CI/CD pipelines covers the operational side well. 如果您需要围绕旋转和 CI 处理的更广泛的过程,__CAPGO_KEEP_0__关于在 CI/CD pipeline 中管理密钥的指南,很好地涵盖了运营侧。

A short walkthrough helps if you want to see the moving pieces in action:

如果您想看到动态部分的行动,一个短的演练会有所帮助:

What I’d change for a live system

我会对一个真正的系统做出什么改变:

Flask 适合作为一个干净的 Web 钩子示例,因为请求处理是明确的,Python 的标准库已经为您提供了 HMAC 所需的内容。

主要要记住的就是 Node 中一样的东西。 验证请求的原始字节,而不是解析的 JSON 字典。

带有签名和时间戳检查的 Flask 示例

import os
import time
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "")

def is_fresh(timestamp_header, tolerance_seconds=300):
    try:
        timestamp = int(timestamp_header)
    except (TypeError, ValueError):
        return False

    now = int(time.time())
    return abs(now - timestamp) <= tolerance_seconds

def verify_signature(raw_body, secret, received_signature):
    expected = hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, received_signature)

@app.route("/webhooks/example", methods=["POST"])
def webhook():
    if not WEBHOOK_SECRET:
        return "Webhook secret is not configured", 500

    signature = request.headers.get("X-Webhook-Signature")
    timestamp = request.headers.get("X-Webhook-Timestamp")

    if not signature or not timestamp:
        return "Missing required security headers", 400

    if not is_fresh(timestamp):
        return "Stale webhook", 401

    raw_body = request.get_data()

    if not verify_signature(raw_body, WEBHOOK_SECRET, signature):
        return "Invalid signature", 401

    payload = request.get_json(silent=True) or {}

    # Acknowledge receipt
    response = jsonify({"status": "ok"})

    # In production, queue payload here instead of heavy sync work
    print("Accepted event:", payload.get("event"), payload.get("id"))

    return response, 200

if __name__ == "__main__":
    app.run(port=5000, debug=True)

Flask 特有的细节

request.get_data() 这是关键的调用。 它为您提供了原始的请求体字节。 如果您直接跳转到 request.json,您已经越过了签名不匹配的界限,变得令人困惑。

一些实现注意事项:

  • 使用 hmac.compare_digest 而不是平等。
  • 将缺失的头部视为客户端失败 并拒绝早期。
  • 使用 silent=True for JSON parsing 如果您想控制错误处理而不是让Flask抛出异常。
  • 保持路由薄. 如果载荷触发任何昂贵的操作,则在队列中排队工作

不要通过放宽安全检查来调试签名不匹配。通过打印您所计算的原始字节以及提供商期望的格式来调试它们。

团队通常会卡在哪里

常见的失败路径是使用手工构建的 JSON 体进行测试,然后切换到真实的提供商并发现签名不再匹配。通常这意味着其中一个三件事情:提供商签署了带有时间戳的封velope,签名以您假设的方式编码不同,或者中间件在验证之前修改了体

当发生这种情况时,停止随机更改加密code。捕获原始头和原始体, reproduce 在一个小型隔离脚本中 hash,然后将其放回 Flask 路由

在 Go 中构建 Webhook 接收器

Go 是一个很好的选择来接收 Webhook,因为标准库足够。您不需要框架来获得一个小型可靠的处理程序,而且code很容易保持诚实

需要小心的是体处理 r.Body 是流。读取它一次,计算您获得的字节的哈希,然后从那些相同字节中反序列化

A standard library example

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/hex"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

type WebhookPayload struct {
	Event string          `json:"event"`
	ID    string          `json:"id"`
	Data  json.RawMessage `json:"data"`
}

func isFresh(timestampHeader string, toleranceSeconds int64) bool {
	ts, err := strconv.ParseInt(timestampHeader, 10, 64)
	if err != nil {
		return false
	}

	now := time.Now().Unix()
	diff := now - ts
	if diff < 0 {
		diff = -diff
	}

	return diff <= toleranceSeconds
}

func verifySignature(rawBody []byte, secret string, received string) bool {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write(rawBody)
	expected := hex.EncodeToString(mac.Sum(nil))

	if len(expected) != len(received) {
		return false
	}

	return subtle.ConstantTimeCompare([]byte(expected), []byte(received)) == 1
}

func webhookHandler(secret string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
			return
		}

		signature := r.Header.Get("X-Webhook-Signature")
		timestamp := r.Header.Get("X-Webhook-Timestamp")

		if signature == "" || timestamp == "" {
			http.Error(w, "missing required security headers", http.StatusBadRequest)
			return
		}

		if !isFresh(timestamp, 300) {
			http.Error(w, "stale webhook", http.StatusUnauthorized)
			return
		}

		rawBody, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "failed to read body", http.StatusBadRequest)
			return
		}

		if !verifySignature(rawBody, secret, signature) {
			http.Error(w, "invalid signature", http.StatusUnauthorized)
			return
		}

		var payload WebhookPayload
		if err := json.Unmarshal(rawBody, &payload); err != nil {
			http.Error(w, "invalid json", http.StatusBadRequest)
			return
		}

		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))

		log.Printf("accepted event=%s id=%s", payload.Event, payload.ID)
	}
}

func main() {
	secret := os.Getenv("WEBHOOK_SECRET")
	if secret == "" {
		log.Fatal("WEBHOOK_SECRET is not set")
	}

	http.HandleFunc("/webhooks/example", webhookHandler(secret))

	log.Println("listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

为什么 Go 在这里感觉坚实

几个优点值得注意:

  • 处理器是明确的. 没有隐藏的中间件魔法。
  • 边缘的类型帮助. 头部解析、时间戳转换和 JSON 解码都失败了。
  • 标准的加密包就足够了. 基本的 HMAC 验证不需要额外的依赖。

操作说明

如果 webhook 的流量增长,Go 的并发模型给你足够的空间来分散后台工作而不改变你的 HTTP 入口点。即使这样,也要保持接收器狭窄。接受、验证、确认,然后转交。

我看到的最强大的 Go webhook 处理器都保持着平淡。它们不混合运输验证与业务逻辑,并且它们不在响应返回之前进行数据库密集型工作。

Debugging 基础知识

通常,一个 webhook 错误会以支持信息的形式出现,而不是堆栈跟踪。 供应商说他们已经传递了事件。 你的端点说没有事件到达应用程序,或者在一个看起来在第一眼有效的请求上,签名验证失败。 到那时,调试就是重建精确的 HTTP 交换,字节为字节,证明它在哪里打破了。

软件开发环境中用于调试 webhook 的五种必备工具和技术的列表。

调试工具箱

从wire格式开始。

如果签名检查失败,捕获原始请求正文,包括用于验证的头部。 在实践中,bug往往是乏味的。 框架在散列之前解析了 JSON,代理更改了编码,或者测试重放忽略了原始时间戳头。 只记录解析的对象是不够的。 你需要原始字节和验证输入。

这些工具可以快速分离问题:

  • 原始请求捕获. 在调查期间记录头部、内容类型、内容长度和未修改的正文。
  • 请求检查端点. 服务如 webhook.site 可以确认发送者传输的内容。
  • Local tunneling. ngrok 和类似工具让您在保留提供者在循环时测试本地接收器。
  • Manual replay. 使用相同的请求体和头部重建请求,或者使用 Postman。 这是确认您的 __CAPGO_KEEP_0__ 或提供者负载是否是问题的最快方法。 curl or Postman using the same body and headers. That is the quickest way to confirm whether your code or the provider payload is the issue.
  • . 发送者仪表盘通常包括响应代码、重试历史和请求标识符,您可以将其与您的日志匹配。The pattern matters. Work from the outside in. First verify the provider sent what you expected. Then verify your server received the same bytes. Then verify your __CAPGO_KEEP_0__ hashed the same bytes with the same secret and timestamp rules.

模式很重要。 从外部开始。 首先验证提供者发送了您期望的内容。 然后验证您的服务器接收了相同的字节。 然后验证您的 code 使用相同的密钥和时间戳规则对相同的字节进行了散列。

Logging that actually helps

良好的 webhook 日志应该在一个搜索中回答三个问题:

Question有用的日志字段
请求到达了吗?路由, 方法, 接收时间
为什么被拒绝?缺少头信息, 过期时间戳, 签名失败
我可以在之后关联吗?事件 ID, 提供者请求 ID

第四个字段在实际系统中会有所帮助。添加一个本地 request_id 由接收者生成的,以便您可以通过应用程序、队列和工作者日志跟踪请求。

要么存储所有内容,要么什么都不存储。不要存储机密信息。避免将包含客户信息、访问令牌或账单详细信息的完整生产负载全部存储。如果您必须存储它们,请只存储元数据加上一个短的体积哈希。这样仍然允许您比较重试和验证两个交付是否相同。

用原始输入重现失败

这是基本教程跳过的部分。如果您无法精确重现失败的请求,那么您只是在猜测。

保存一个失败的 webhook 为:

  • 原始数据字节
  • 所有与签名相关的头部
  • 请求时间戳
  • 内容类型
  • 服务端请求ID

然后将其重放到一个测试环境中。如果重放成功,请比较传输过程中发生的变化。常见的原因包括中间件对请求体的规范化、字符编码不符以及负载均衡器对请求头的剥离或重写。另外,我还见过由于团队从美化后的仪表盘视图中复制了有效载荷,而不是实际请求体,导致的失败。仅仅是空格的差异就足以破坏HMAC验证。

为了更广泛的发布和移动传输调试,相同的调试纪律在Capgo的指南中 tools for debugging OTA updates in Capacitor调试OTA更新的code

不同传输方式,相同的教训。捕获请求路径的实际值之前不要改变应用程序code。

如果签名验证失败,请检查原始字节、用于验证的精确头部以及时间戳值之前不要接触加密__CAPGO_KEEP_0__。

生产就绪的Webhook检查表

安全性和正确性检查

  • 验证每个请求签名. API端点URL泄露。测试URL在聊天中被分享。签名验证是告诉您发送者知道共享密钥的控制。
  • 拒绝旧请求. 有效的签名可以在旧载荷上重放。强制执行与提供商重试模型匹配的时间戳容差。
  • 对原始体进行散列,而不是解析的JSON. 中间件可以重新排序键、规范空格或更改编码。验证必须在到达的确切字节上运行。
  • 将签名密钥保留在code中. 环境变量是基础。 如果您定期轮换凭据或跨多个环境运行,则使用密钥管理器是一个更好的选择。
  • 在认证错误时失败. 如果签名头缺失、格式错误或使用了意外的方案,拒绝请求并记录原因。

可靠性检查

  • 认可快速. 供应商通常将任何 2xx 视为成功,因此验证请求、持久化所需内容,并将慢速工作转移到队列或工作者中。
  • 使处理程序幂等. 同一事件可能会多次到达。 将事件 ID、传递 ID 或稳定的供应商标识符作为关键副作用。
  • 返回可预测的错误代码. 对于输入不正确时使用 400 ,对于验证失败时使用 401 ,并且只有当系统出现问题时才使用。 这使得供应商重试行为更容易理解。 403 在解析之前设置限制 5xx . 在请求大小、内容类型和标头计数方面提前设置 Cap。 这可以防止 webhook 端点变成通用 ingestion hole。
  • Make handlers idempotentReturn predictable error codes
  • 保持合同狭窄. 只接受您支持的字段和事件类型。 懒散的解析在一开始可能很方便,但在提供商API发生变化时会变得昂贵。

可观察性检查

良好的Webhook操作看起来很乏味。 团队可以快速回答三个问题:我们是否接收到了它? 我们是否验证了它? 下游处理是否成功?

使用该标准:

  • 跟踪接收、验证和处理作为单独的结果.
  • 记录请求ID、事件ID、签名状态和时间戳偏差.
  • 衡量队列延迟、处理器延迟和重试量.
  • 保持一个安全的重放路径用于阶段或重新交付工作流.
  • 在模式变化时警报,例如签名失败的激增或重复交付__CAPGO_KEEP_0__是一个有用的例子,展示了更广泛的运营点。 它包括工具围绕发布交付和可观察性在其更新工作流中,以及其生态系统的一部分也触及Webhook相关流。 这个教训是实用的。 交付系统需要从接收到完成的可见性。

Capgo

如果团队覆盖了上述检查,webhook接收器通常在生产环境中处于良好状态。如果任何项缺失,那么缺口通常会在事故发生时而不是在演示中显示出来。

常见问题:关于Webhook的FAQ

我应该返回什么状态码code?

返回一个 2xx 当您接受了webhook时。如果验证失败,请返回一个与失败相匹配的客户端或认证错误,例如 400 对于输入不正确时或 401 对于无效的认证数据时。保持该逻辑一致,以便供应商仪表板更容易解释。

我应该同步处理webhook吗?

通常不。验证它,确认它,然后将实际工作推送到队列或后台工作线程中。这会保持交付路径快速并减少由于下游处理速度慢而导致的重复重试。

如何处理重试?

假设它们会发生。将无副作用的事件处理程序设计为接收相同事件不会导致重复副作用。事件ID或供应商交付ID通常是用于实现这一点的锚点。

如果事件到达顺序不对怎么办?

在可以的情况下,设计处理程序以容忍顺序。 如果商业流程需要顺序,则持久化足够的状态以检测陈旧的转换,而不是假设交付顺序反映事件顺序。

如何处理 webhook 版本变化?

有意地版本化处理逻辑。 将供应商特定的解析隔离,避免在代码库中散布负载假设,并在推出对新格式的支持之前添加测试,使用真实捕获的样本。


如果您的团队部署 Capacitor 或 Electron 应用程序, Capgo 值得了解的原因是它为团队提供了一个控制的方式来交付签名的 Web 更新、观察发布行为和在等待应用商店审查时恢复从事故。 这与坚实的 webhook 设计背后的工程本能一致:验证输入、使发布路径可观察并使恢复快速。

实时更新 Capacitor 应用

当 web 层 bug 活跃时,通过 Capgo 将修复推送到用户,而不是等待几天的 app store 审批。用户在后台接收更新,而原生变化仍在正常审批路径中。

立即开始

最新博客文章

Capgo 给您需要创建真正专业的移动应用所需的最佳见解。