Webhooks 与 轮询:如何选择合适的集成方式
Webhooks 与 轮询:了解它们如何影响延迟、失败、速率限制,以及保持数据同步的重试与回放模式。

我们在同步数据时要解决什么问题?
“同步”听起来像是“让更新尽快出现”,但真实的任务更难:要让两个系统在消息迟到、重复或丢失时仍能达成一致。
在 webhook 与 轮询(polling)之间,区别在于你如何得知某件事发生了。
Webhook 是推送。系统 A 在发生事件时调用你的端点(例如 “invoice paid”)。轮询是拉取。你的系统按计划询问系统 A:“自上次以来有什么新内容吗?”
保持系统同步通常意味着同时跟踪事件和状态。事件告诉你发生了什么,状态告诉你记录现在是什么样子。时序很重要,因为集成很少按完美顺序运行。一个“已更新”事件可能在“已创建”之前到达,或者重复到达,或者根本没到达。
目标是正确的数据,而不仅仅是新鲜的数据。“正确”意味着你不会错过更改,不会重复应用相同更改,在停机后可以自动恢复而无需手动清理,并且你能证明自己何时处理了哪些内容。
一个实际例子:你的支付提供方发送一个像 “payment_succeeded” 的 webhook。你的应用创建订单并标记为已支付。如果你的 webhook 端点短暂不可用,你可能永远看不到该事件。定期轮询一个“自昨天以来有更新的支付”任务可以弥补这个缺口并修正订单状态。
大多数真实的集成最终会同时使用两者:用 webhooks 提速,用轮询做回填和校验。方法不如围绕它们的安全护栏重要。
延迟与新鲜度:更新实际上有多快到达
当人们比较 webhook 与 轮询时,他们通常关注一点:你的应用多快能注意到别处的更改。这种新鲜度不仅是可有可无的,它会影响支持工单、重复工作,以及用户是否信任看到的数据。
轮询有固有延迟,因为你只能按计划去询问。如果你每 5 分钟轮询一次,更新可能比实际发生晚几秒到将近 5 分钟,加上 API 响应时间。更频繁地轮询能提升新鲜度,但会增加 API 调用、成本,并提高触及速率限制的概率。
Webhook 看起来接近实时,因为提供方会在事情发生后尽快推送事件。但它们并非瞬时或有绝对保证。提供方可能会批量发送事件、稍后重试,或暂停投递。你的系统也会增加延迟(队列积压、数据库锁、部署等)。更准确的预期是:正常时很快,异常时最终一致。
流量形态很重要。轮询带来稳定、可预测的负载。Webhook 则是突发性的:一个繁忙的小时内可能在一分钟内发送数百条事件,然后又长时间没有。如果你接受 webhook,假设会有峰值并规划将事件入队,以便按可控速率处理。
在设计之前,先选定一个目标新鲜度窗口:
- 秒级:面向用户的通知、聊天、支付状态
- 分钟级:支持工具、管理视图、轻量报告
- 小时级:夜间对账、低优先级分析
如果销售团队需要在 1 分钟内看到新线索,webhook 能满足。每隔几小时运行的“安全轮询”仍可捕获错过的事件并确认最终状态。
你在生产中会实际看到的失败模式
大多数集成会以枯燥但可复现的方式失败。令人惊讶的不是会出问题,而是它会悄无声息地坏掉。速度很容易争论,但可靠性才是真正的工作重点。
轮询的失败常表现为“我们没看到更新”,即便代码看起来没问题。超时可能在请求中途截断,部分响应如果你只检查 HTTP 200 而不验证主体也会溜走。分页规则变化很常见:API 改变排序、页规则或从页码改为游标,你可能会跳过或重复读项。另一个经典错误是用本地时钟过滤“updated_since”,当时钟漂移或提供方使用不同时间字段时会错过更新。
Webhook 的失败模式不同。投递通常是“至少一次”(at least once),提供方在网络错误时会重试,所以你会看到重复。如果你的端点宕机 10 分钟,之后可能会看到一批过期事件。签名验证问题也很常见:密钥轮换、验证了错误的原始负载,或者代理修改了头部,导致原本有效的事件被判为无效。
两种方法共同的失败模式是重复和乱序投递。假设你会收到同一事件多次、事件迟到、事件乱序到达,以及负载中缺少你期望的字段。
你几乎永远不会得到“恰好一次”。要按“至少一次”来设计,并使处理安全。存储幂等键(事件 ID 或提供方对象版本),忽略重复,只在更新比已存更新时应用更新。还要记录你收到的内容和处理结果,这样你可以自信地回放而不是猜测。
速率限制与成本:控制 API 使用量
速率限制是 webhook 与 轮询从理论变成预算与可靠性问题的地方。每次额外请求都花时间和金钱,也可能损害你与提供方的关系。
轮询会消耗配额,因为即便没有变化你也要付出检查的代价。当限制按用户或令牌划分时更加糟糕:1000 个客户每分钟轮询一次可能会被看作攻击,尽管每个客户都“表现良好”。轮询还容易成倍增加调用(先调用列表端点,再为每个项目获取详情),这就是你出乎意料触达上限的方式。
Webhook 通常能减少 API 调用,但会产生突发压力。提供方可能在故障后、批量导入或产品发布时一次性发送成千上万条事件。有些会用 429 限流,有些会频繁重试,还有些如果你的端点慢了就会丢弃事件。你的系统需要背压:快速接受、入队处理,并以安全速率消费。
为在不丢失正确性的前提下降低调用次数,可以采纳一些经得起考验的方案:
- 使用“自上次以来更新”时间戳或变更令牌做增量同步
- 服务端过滤(只订阅你需要的事件类型)
- 批量读取与写入(分块获取详情,批量写入)
- 缓存稳定的参考数据(套餐、状态列表、用户资料)
- 将“实时”需求与“报告”需求分离(快速路径 vs 夜间任务)
在高峰到来之前就做预案。保留一个专门的回填模式,跑得慢一些、遵守配额,并能暂停与恢复。
如何选择:一个简单的决策指南
webhook 与 轮询的选择通常归结为:你需要速度,还是需要一种在供应商不可靠时仍能获得可预测更新的简单方法?
当第三方不提供 webhook,或你的工作流能容忍延迟时,轮询往往是更好的默认选择。如果你只需要按日或按小时同步,且 API 有清晰的“updated since” 过滤,那轮询也很容易推理与实现。
当时间很重要时(“收到新订单”、“支付失败”、“工单被指派”),webhook 是更好的默认选择。它们减少了无谓的 API 调用,并能立即触发后续工作。
一个实用规则是:在正确性重要时同时使用两者。让 webhook 提供速度,让轮询清理遗漏。例如:快速处理 webhook,然后每 15 分钟(或每几小时)运行一次定时轮询来弥补因事件丢失或短暂故障造成的差距。
快速指南:
- 如果几分钟的延迟可以接受,从轮询开始。
- 如果必须在几秒内显示更新,从 webhook 开始。
- 如果供应商不可靠或事件很关键,计划使用 webhook + 轮询。
- 如果 API 限制严格,优先考虑 webhook 加轻量轮询。
- 如果数据量大,避免频繁的全量轮询。
在最终决定前,向供应商问清楚几件事并得到明确答复:
- 存在哪些事件类型,它们是否完整(创建、更新、删除)?
- 他们会重试 webhook 吗?重试多长时间?
- 是否可以回放事件或按时间范围获取事件历史?
- 他们是否对 webhook 请求签名以便你验证真实性?
- 他们是否支持“updated since” 查询以便高效轮询?
逐步:设计一个能保持正确性的同步
“正确”的同步不仅仅是“数据出现”。它意味着正确的记录匹配、最近的更改生效、并且在出现问题时你能证明发生了什么。
从一个可以测试和监控的计划开始:
- 定义真源与规则。选定哪个系统拥有每个字段。例如 CRM 拥有客户姓名,但你的计费工具拥有订阅状态。决定“足够新”的含义(例如“在 5 分钟内”)以及可以接受的错误类型。
- 选择稳定的标识符。把第三方的唯一 ID 与你内部 ID 一起存储。避免用邮箱或姓名作为主键(它们会变)。如有可能,保存版本号或“updated_at” 时间戳以检测更新。
- 规划初始导入,然后做增量更新。把第一次导入当作单独任务并加入检查点以便能恢复。之后仅处理变化(事件、“since” 查询或两者结合),并记录一个游标比如“上次成功同步时间”。
- 有意识地处理删除与合并。决定删除是彻底移除、归档还是标记为不活跃。合并时选择保留哪个 ID 并保留审计记录。
- 设置监控信号。跟踪同步延迟、失败调用与卡住的队列。当延迟超过阈值时报警,而不仅是在某服务崩溃时报警。
实现时把这些选择在数据模型中保持可见:外部 ID、时间戳、状态字段和存放同步检查点的位置。正是这些结构在实际情况混乱时保持同步的正确性。
幂等性与顺序:可靠集成的核心
长期构建 webhook 与 轮询集成会发现一条规律:你会看到重复、重试与乱序更新。如果你的同步无法安全地重复处理相同消息,它会随着时间漂移。
幂等性意味着“相同输入,得到相同结果”,即使它到达两次。把每个入站事件当作“可能重复”的,设计你的处理器要安全。常见模式是:计算去重键,检查是否已处理过,然后再应用变更。
去重键各有权衡。事件 ID 是最佳选择(如果提供方提供)。如果没有,可以使用对象版本(如递增修订号)。像“在 10 分钟内忽略重复”的时间窗口很脆弱,因为会有迟到到达。
顺序是另一半。全局顺序罕见,所以目标是按对象顺序。只有在版本比你已存的更新时才对单个工单、发票或客户应用更新。如果没有版本,采用“最后写入获胜”的明确规则(例如更新的 updated_at 覆盖),并接受时钟偏差可能导致边缘情况。
存储足够的信息以便恢复与回放而无需猜测:
- 每个对象的“最后看到”版本或 updated_at
- 轮询任务的最后处理游标或检查点
- 已处理事件 ID 的表(若增长快可设保留策略)
- 短期保留原始负载以便重新运行修复
示例:Stripe 的付款 webhook 到达两次,然后“paid”更新在“created”事件之前到达。如果你存储了发票的最新状态版本并忽略更旧的更新,结果就是正确的。
防止无声数据漂移的重试与回放模式
大多数集成都悄悄地失败。Webhook 迟到、轮询任务命中限制,或你的应用在保存时超时。没有重试与回放,系统会慢慢产生差异,直到客户抱怨为止。
Webhook 重试:快速接受,安全处理
当你未能快速返回成功 HTTP 状态时,提供方通常会重试。把 webhook 请求当作一次投递通知,而不是做繁重工作的地方。
一个实用的 webhook 模式:
- 在基本验证(签名、模式、时间戳)通过后,尽快以 2xx 响应。
- 把事件存储并标记为待处理,确保有唯一 ID。
- 异步由 worker 处理并记录尝试次数。
- 对临时错误稍后重试,对永久错误停止并报警。
- 对坏数据返回 4xx,仅在服务器真实问题时返回 5xx。
这样可以避免一个常见陷阱:以为“收到 webhook”就等于“数据已同步”。
轮询重试:对 API 保持礼貌
轮询的失败方式不同。风险在于短暂故障后一窝蜂的重试会让速率限制雪上加霜。使用指数退避并加入抖动(jitter),并保留“since”游标以避免重新扫描全部数据。
当现在无法处理某项内容时,把它放入死信队列(或表)并记录原因。这样你就有一个安全的地方检查、修正映射规则并在不猜测丢失内容的情况下重新运行。
回放是修复错过事件的方式。一个简单的回放策略:
- 选择时间窗口(例如过去 24 小时)或一组受影响记录。
- 从提供方重新获取当前状态。
- 幂等地重新应用更新并修正不匹配项。
- 记录发生了什么以及原因。
示例:你的计费提供方发送了 "invoice.paid",但你的数据库在那 30 秒内被锁定。你把事件放到死信队列,然后通过重新获取发票与支付状态来回放并更新你的记录以匹配实际情况。
常见错误及避免方法
多数同步错误不是“大架构”问题,而是一些小假设累积成的静默漂移、重复记录或遗漏更新。
一些常见问题:
- 轮询过于频繁却没有增量过滤。跟踪游标(updated_at、事件 ID、页令牌),只请求自上次成功运行后的变化。
- 把 webhooks 当作有保证的投递。保留回填任务以重新检查最近的历史(例如最近 24–72 小时)并对账你可能错过的内容。
- 忽视重复。确保每次写入都是幂等的。存储提供方事件 ID(或稳定的外部 ID)并拒绝重复应用相同变更。
- 接受 webhook 调用却不验证。验证提供方提供的签名令牌或校验方法。
- 在同步健康上盲目行事。跟踪延迟、积压大小、上次成功运行时间和错误率。当延迟超过阈值时报警。
很多“webhooks vs polling”的争论忽略了重点:可靠性来自任一方法周围的安全护栏。一个支付 webhook 可能重复到达或迟到。如果你的系统直接在 webhook 中创建记录但没有幂等性,可能会重复发送消息或重复收费客户。
健康集成的快速检查清单
日常健康检查在使用 webhook、轮询或两者时都类似。你需要知道数据是否新鲜、错误是否堆积、以及是否能干净地恢复。
一个可在几分钟内完成的快速检查清单:
- 新鲜度:将“上次接收事件”或“上次轮询完成”与预期延迟比较。
- 失败:查找不断上升的重试次数或长时间未移动的任务。把错误计数与“上次成功”时间配对查看。
- 配额:检查已用 API 调用数和剩余额度。如果接近上限,放慢轮询并批量请求。
- 正确性:抽查系统间总数(例如“今日订单”)并抽样几个最近记录。
- 恢复准备:确认你能在不产生重复或遗漏的情况下安全地重处理最近时间窗口。
一个有用的习惯是定期在受控环境回放一个已知的繁忙时段,并确认结果与生产一致。
示例:为现实工作流混合使用 webhook 与轮询
想象一个小型 SaaS 团队需要将三个系统保持同步:CRM(联系人与交易)、Stripe 支付(收费与退款)和支持工具(工单状态)。
他们对需要快速响应的内容采用 webhook 优先策略。CRM 事件更新客户记录并触发内部任务。Stripe webhook 创建发票、在付款后解锁功能,并在扣款失败时标记账户逾期。对于支持工具,他们如果有 webhook 就用 webhook,但也保留定时轮询,因为工单状态可能会批量变化。
他们把轮询当成安全网,而不是主力引擎。每晚一个对账任务会拉取过去 24 小时的变化并与应用已存数据比较。
然后发生了真实的故障:部署期间他们的 webhook 端点宕机 20 分钟。
- CRM 和 Stripe 会重试投递一段时间。
- 有些事件会迟到、有些乱序到达,有些可能过期无法投递。
- 对账轮询检测到差距(缺失事件 ID 或不匹配的总数)并回填缺失更改。
他们记录的内容包括:入站事件 ID、提供方时间戳、内部记录 ID 以及最终结果(已创建、已更新、已忽略)。触发告警的条件包括:持续的 webhook 失败、重试激增,或对账发现超过小阈值的缺失更新。
下一步:实现、监控并迭代
对大多数团队来说,一个实用的默认做法很简单:用 webhook 获得即时性,并保留一个小型轮询任务用于对账。Webhook 将更改快速送达你这里,轮询能捕获因故障、订阅配置错误或提供方偶尔丢失事件而错过的内容。
在追求速度之前先做正确性。把每次入站变更都当作可以安全重复应用的东西。
首先采取的三项行动:
- 将提供方的事件与字段映射到你内部模型,包含“删除”、“退款”或“状态变更”在你系统中的含义。
- 从第一天起设计幂等性:存储外部事件 ID 或版本,使每次更新可回放且不会产生重复。
- 有意添加回放机制:保留“上次看到”游标或时间窗口轮询,并构建管理界面以便在出现异常时重跑某个时间段。
系统运行后,监控是保持运行的关键。跟踪 webhook 投递率、按原因分类的失败(超时、4xx、5xx),以及你的对账轮询落后的程度。对“未收到事件”和“收到过多事件”都要报警。
如果你希望在不从头手写完整后端的情况下构建这些功能,AppMaster (appmaster.io) 是一个无代码选项,它可以让你建模数据、创建 webhook 端点并用可视化工具设计重试/回放流程,同时仍能生成可部署的真实源代码。


