Webhook 投递保证详解:重试、顺序与重复
Webhook 服务商实际承诺什么:至少一次投递、重试、不保证顺序、可能重复。另外说明如何构建能承受这一切的接收端,并附有一份适用于包括加密支付事件在内的任何 Webhook 集成的检查清单,同时说明如何与服务商的 API 对账。
审核: Maurice Sobiera

Webhook 投递保证描述的是服务商向您的端点投递事件的频率与顺序。大多数服务商最多只承诺至少一次投递。您的端点会收到绝大多数事件,但有时是两次,有时延迟,有时乱序。可靠的集成会把保障措施放在接收端。
本指南以通俗的方式解释每一项保证,展示真实服务商文档中的承诺,最后给出一份适用于任何 Webhook 集成(包括加密支付事件)的检查清单。
要点速览
- 至少一次是现实的上限。服务商会一直重试,直到您返回 2xx 响应,因此重复是常态,您必须处理它们。
- 不保证顺序。较早的超时事件可能在较晚的事件之后到达,因此应按事件自身的时间戳或序列字段排序。
- 先确认,后处理。验证签名,将事件持久化,返回 2xx,然后异步完成繁重的工作。
- 以事件 ID 去重。在任何副作用(例如为余额入账)之前,用唯一索引记录该 ID。
- 把 Webhook 视为通知,而不是账本。按计划与 API 对账关键记录,以发现重试遗漏的内容。
- Chaingateway 不会自动重试失败的通知。它会列出这些通知,并应请求重新发送。
三种投递保证意味着什么?
投递语义源自分布式系统,并且可以清晰地映射到 Webhook 上。
| 保证 | 发送方做什么 | 可能出什么问题 | 您需要构建什么 |
|---|---|---|---|
| 至多一次 | 只发送一次,从不重试 | 如果您的端点宕机,事件就会丢失 | 与 API 对账 |
| 至少一次 | 不断重试,直到您确认或重试次数耗尽 | 重复、迟到到达、顺序颠倒 | 幂等处理程序、去重 |
| 恰好一次 | 理论上只投递一次,既不丢失也不重复 | 在不可靠的网络边界上无法实现 | 至少一次加去重 |
在至多一次的模式下,一次失败的尝试就意味着事件丢失。对发送方来说它很简单,但对任何涉及资金的事务而言都不安全。
在至少一次的模式下,发送方会持续尝试,直到收到成功响应,或耗尽重试预算。事件丢失变得罕见,但有一个陷阱。假设您的处理程序为账户入账,然后在返回 200 之前崩溃。发送方从未看到确认,因此会再次发送该事件。从发送方的角度看,这是正确的行为。而从您的角度看,除非您加以防止,否则就会造成重复入账。
恰好一次是每个人都想要的,但没有人能通过 HTTP 作出承诺。连接中断时,发送方无法知道您的服务器是否已处理该请求。实际的替代方案是至少一次投递加上一个忽略重复的接收端。这种组合提供的是恰好一次的效果,而这才是真正重要的。
服务商实际承诺什么?
翻阅任何正规 Webhook 服务商的投递相关文档,都会出现相同的模式。
Adobe I/O Events 表示,每个事件在每次注册中至少投递一次,可能会发送重复事件,且不保证事件顺序。Column 是一家美国银行业 API 服务商,其文档称 Webhook 事件不保证按创建顺序到达。由于每次重试都是独立调度的,一个超时的较早事件可能在较晚的事件之后才到达。
最后这个细节解释了大多数顺序类错误。事件 A 失败一次,被安排在一分钟后重试。之后创建的事件 B 立即成功。您的系统先看到 B,再看到 A。服务商一侧并没有出错,这项保证本来就从未涵盖顺序。
顺序:以事件的时钟为准,而不是投递的时钟
Wise 的 Webhook 指南建议根据事件时间戳重建时间顺序,若有单调递增的序列 ID,则优先使用序列 ID,而不是依据通知的发出时间。其文档指出,发送时间可能与事件实际发生的时间不同,尤其是在重试期间。
实际做法如下:
- 把事件的业务时间戳或序列号与负载一同保存。
- 事件到达时,将其与您为该对象持有的最新状态进行比较。
- 如果到达的事件比您已应用的状态更旧,则记录它,但不要让它覆盖更新的状态。
对于从待处理变为已确认的付款状态,这样可以防止一个迟到的“待处理”事件把已确认的付款回滚。
Webhook 重试如何工作,何时停止?
重试策略各不相同,但形式是一致的:指数退避、上限和硬性停止。Chaingateway 是例外,因为它不会自动重试。
| 服务商 | 重试行为(来自其文档) |
|---|---|
| Column | 三天内最多重试 25 次,指数退避,第一次重试在失败投递后一分钟进行 |
| Probo | 最多 12 次尝试,从 30 秒基数开始的指数退避并采用完全抖动,上限为四小时 |
| Stripe | 生产模式下指数退避,最长可达三天 |
| Chaingateway | 不自动重试。失败的通知列在 GET /api/v2/{chain}/webhooks/notifications/failed,并可通过 POST /api/v2/{chain}/webhooks/notifications/{id}/retry 重新发送 |
来源:Column、Probo 和 Stripe 的文档(2026 年 11 月 5 日查阅),以及 Chaingateway 的 OpenAPI 规范。
Probo 的文档补充了有用的细节。它会对网络错误、超时以及 408、425、429 或 5xx 响应进行重试,并在上限范围内遵守 Retry-After 请求头。尝试次数耗尽后,该投递会被标记为失败,且不会重新入队。
由此得出两点经验。第一,您端点返回的错误码就是信号:5xx 会告诉发送方再试一次,因此只有在您确实希望重试时才返回它。第二,重试窗口是有限的。如果您的服务停机时间超过该窗口,这些事件就会从重试队列中消失,您需要另一种方式来恢复它们。
为什么要快速确认、稍后处理?
当您返回 2xx 状态码时,一次投递即被视为成功。Column 在 10 秒内收到 2xx 即认为事件已投递,并建议在接收后立即返回,而不是先进行大量处理。NestJS 的文档将 2xx 视为成功,而其他一切,包括重定向、超时和连接被拒绝,都视为失败。
因此,处理程序的职责很窄:
- 读取原始请求体。
- 验证签名。
- 检查事件类型和 ID。
- 将事件写入持久化存储(一条数据库记录或一条队列消息)。
- 返回 200、202 或 204。
其他一切,例如更新余额、发送邮件或调用其他服务,都由从该存储读取数据的工作进程完成。如果您把耗时的工作放在同步路径中,一次缓慢的数据库查询就会变成超时,超时变成重试,重试又变成重复。我们在自己关于 Webhook 与 WebSocket 的文章中介绍了接收、队列和扇出的方法。
如何让重复无害?
由于至少一次投递意味着会有重复,您的处理程序必须具备幂等性。标准的做法很简单。
Probo 的指导是先验证签名,确认预期的组织和事件类型,然后在任何副作用之前,以原子方式把事件 ID 记录到一个唯一索引中。之后携带已记录 ID 的请求会被忽略。Stripe 给出的建议相同:记录已处理的事件 ID,并跳过任何已处理过的事件。许多服务商通过在每个负载中包含唯一 ID 来让这件事变得容易。例如,Adobe 在负载中放入 UUID,并在 x-adobe-event-id 请求头中重复一次。
一个最小化的模式:
- 把事件 ID 插入一张带唯一约束的表。
- 如果插入因 ID 已存在而失败,返回 200 并停止。
- 如果插入成功,则将工作放入队列。
顺序很重要:先记录,后行动。如果您先行动,而在记录之前崩溃,重试就会重复该行动。如果您先记录,而在行动之前崩溃,工作进程应在重启时拾取未完成的记录,这样事件就不会被悄悄丢弃。
对于资金流动,请再加一层:让业务操作本身也具备幂等性。以交易哈希加日志索引或付款 ID 作为充值入账的键,这样即使事件表本身出现缺陷,也不会对同一笔付款重复入账。
如何验证每一个请求?
Webhook 端点是一个公共 URL,它会触发您系统中的操作。除非您检查发送方的身份,否则任何人都可以向它发送请求。
Stripe 要求使用原始请求体、签名请求头和您的端点密钥进行验证。Probo 的建议是使用未经修改的请求体,在解析 JSON 之前验证签名,然后再进行时间戳新鲜度检查。在验证之前解析并重新序列化请求体,是签名始终无法匹配的典型原因。
对于对请求体签名的服务商,一套可靠的验证流程如下:
- 使用您的密钥,对收到的确切字节计算 HMAC。
- 使用恒定时间比较,与请求头中的值进行比对。
- 拒绝时间戳超出较短容差窗口的请求,以限制被重放的捕获报文。
- 只有在此之后,才解析 JSON 并查看事件类型。
Chaingateway 的工作方式有所不同。它对负载中的 txid 计算 Base64 编码的 HMAC-SHA256,并放在 X-Signature 请求头中发送。签名文档展示了校验方法,我们关于 Webhook 安全新功能的文章则说明了设置方法。
如何恢复重试遗漏的事件?
即使是完美的接收端,在某些情况下也会丢失事件:停机时间超过重试窗口、防火墙配置错误、部署在数小时内持续返回 500。不同服务商的恢复选项各不相同。Probo 没有手动重放功能,也不会重新排队失败的投递;它期望您对失败发出告警,并与其 API 对账。Stripe 指出,运营者可以手动处理积压的事件以加快恢复。Column 表示,当失败需要对账时,缺失或多出的事件可以通过其事件 API 获取。

ThriveDesk 开发者文档明确阐述的常见设计原则是:把 Webhook 视为通知,而不是唯一的事件账本。将负载持久化保存,异步处理,并定期用 REST API 核对关键记录。
一个对账任务可以很小:
- 按计划,从服务商的 API 列出最近一段时间内重要的对象(付款、钱包、订单)。
- 将它们的状态与您自己的记录进行比较。
- 通过您 Webhook 处理程序所用的同一幂等代码路径,应用任何差异。
- 如果对账任务频繁发现缺口,就发出告警,因为这说明端点存在问题。
由于后续事件通常携带对象的完整当前状态,因此一个遗漏的中间事件,无论如何都可能被下一个事件覆盖。对账用于覆盖那些不存在“下一个事件”的情况。
这些保证如何适用于加密支付事件?
链上付款提高了风险,因为一次遗漏或重复的事件,就意味着一次遗漏或重复的入账。无论使用哪家服务商,以下几个习惯都有帮助:
- 以链上标识符作为入账的键。 使用交易哈希加其中的转账索引,而不仅仅是 Webhook 的投递 ID。
- 把状态建模为递进过程。 待处理、已确认和最终状态是有顺序的状态。切勿让较旧的状态覆盖较新的状态。
- 与您自己的数据匹配。 入账前,在您的数据库中查找接收地址或引用;忽略您并不拥有的地址上的事件。
- 对账余额。 定期将您的账本与 API 提供的钱包余额进行比较,可以发现事件流遗漏的任何内容。
我们关于使用 Webhook 自动处理充值的详细说明把这些步骤放入了付款流程,而 Polygon Webhook 指南则展示了在特定链上替代轮询的事件驱动方案。
我们的看法
我们为处理资金的开发者构建 Webhook,因此每天都在思考投递保证。我们的看法是,没有任何诚实的服务商能够承诺恰好一次投递,正确的回应是一份清晰的约定:事件是通知,已签名的请求证明其来源,而 API 始终是事实的来源。
这就是为什么我们把实时事件通知与跨链统一的 REST API 配对提供。Webhook 告诉您某件事发生了,API 则让您确认它、获取已解码的交易数据,并在需要时进行对账。我们的 Webhook 经过 HMAC 签名,以便您的接收端能够验证;负载中包含已解码的发送方、接收方、代币合约和金额,因此您的处理程序可以把时间花在业务逻辑上,而不是解析原始日志。
我们也认为,团队不应为了实现这一点而必须运行自己的节点,或应付各链特定的端点。在以太坊、比特币、Tron、Solana、Polygon、BNB Chain 和 Arbitrum 之间使用一套集成、一种签名方案和一种查询状态的方式,意味着投递的边界情况可藏身之处更少。
Bitblade
基于已签名的多链 Webhook 构建
如果您要在应用中加入加密支付,Chaingateway Webhooks 会以 HMAC 签名投递区块链事件通知,并由一个 区块链 API 支持,用于钱包、转账、已解码交易和 gas 价格。价格页面上的套餐中,Plus 每月 49 欧元起,Pro 每月 99 欧元,Premium 每月 249 欧元,Enterprise 每月 499 欧元;按年计费则从 490 欧元起。

将 Webhook 流与上述对账模式配合使用,您就能获得快速的通知,并拥有安全网。
常见问题
能否实现恰好一次的 Webhook 投递?
在不可靠的 HTTP 连接上无法实现。当响应丢失时,发送方无法判断您的服务器是否已处理该请求。实际可行的做法是至少一次投递,加上基于事件 ID 的去重,从而在您的系统中产生恰好一次的效果。
我的 Webhook 端点应返回什么状态码?
一旦您验证并持久化存储了事件,就应立即返回 2xx 状态码,例如 200、202 或 204。像 Column 这样的服务商只在 10 秒内收到 2xx 时才将投递计为成功。只有在您希望发送方重试时,才返回 5xx。
为什么 Webhook 事件会乱序到达?
重试是按事件独立调度的。如果事件 A 超时,而事件 B 成功,那么 B 会先到达,A 则在其重试之后才出现。Adobe 和 Column 等服务商明确表示不保证顺序,因此请按事件的时间戳或序列字段进行排序。
Webhook 发送时我的服务器宕机了会怎样?
大多数服务商会在有限的窗口内以指数退避方式重试,在我们查阅的文档中,窗口最长约为三天。之后,该事件可能不会被重新入队。Chaingateway 不会自动重试:它会列出失败的通知,并在您请求时重新发送。无论哪种情况,都应定期与服务商的 API 进行对账,以恢复任何遗漏的内容。
我需要验证 Webhook 签名吗?
需要。您的端点可以被公开访问,而使用原始请求体和您的密钥进行签名校验,是确认请求来自服务商的方式。请添加时间戳容差,以降低重放风险。
来源
- Adobe I/O Events 常见问题
- Column:事件与 Webhook
- Wise Platform:事件顺序
- Probo 文档:Webhook 投递与恢复
- Stripe 文档:Webhook
- NestJS:Webhook
- ThriveDesk 开发者文档:重试、失败与重放
推荐阅读
准备好自己动手构建了吗? 获取你的 API key — 7 天试用,无需信用卡 — 或查看 区块链 API 获取完整的 endpoint 参考。