常见的说法是:轮询是入门的笨办法,Webhook 才是进阶之后该用的。这个说法很工整,却常常出人意料地把团队引向错误的架构。
有用的问题不是哪个更高效。Webhook 更高效,而且差距很大。有用的问题是每一种出故障时会怎样,因为凌晨 3 点你要面对的正是这个特性,而这两者几乎完全相反。
TL;DR
- 轮询的失败动静很大,而且可恢复:你会注意到,下一次轮询也会把数据补上
- Webhook 的失败静默,而且永久:没有任何报错,事件就这么没了
- Webhook 逼你自己搭建轮询免费附带的基础设施:重试、去重、顺序和签名验证
- 大多数生产环境的集成最后都是混合方案:Webhook 负责时效,对账轮询负责正确性
- 如果只能做一种,而且正确性比时效更重要,那就轮询
通常给出的对比
| 轮询 | Webhook | |
|---|---|---|
| 时延 | 取决于轮询间隔,平均是间隔的一半 | 接近实时 |
| 浪费的请求 | 大部分 | 没有 |
| 谁承担成本 | 你 | 发送方 |
| 需要公网端点 | 否 | 是 |
| 顺序 | 由你控制 | 不保证 |
| 搭建 | 一个 HTTP 调用加一个循环 | 端点、TLS、签名验证、去重、重试 |
看这张表,Webhook 在大家通常最在意的那几行上都赢了。所以才有那么多团队换过去、上线,然后在两个月里悄悄丢掉事件。
真正起决定作用的对比
换个问题问:它出故障的时候,我怎么知道?
轮询的降级方式会自己暴露出来。你的服务器宕机了,循环就不跑。服务器恢复后,下一次轮询会问“从我上一个游标之后有什么变化”,然后拿到全部。恢复是自动的,不需要任何你原本没写过的代码。如果 API 挂了,你的轮询会收到 500,你马上就能在自己的错误率里看到。拉取模式意味着你始终知道自己最后一个确认无误的位置,光这一个特性就价值巨大。
Webhook 的失败方式在你这一侧是看不见的。一次糟糕的部署让你的端点连续六个小时返回 500。平台重试、放弃,然后就不管了。你的日志里看不出任何异常,因为你没法记录一个从没收到过的请求。你的系统里没有游标,没有缺口检测,也没有任何地方显示“我落后了 400 个事件”这样的数字。没有数据和没有活动看起来完全一样,在一个安静的周日,你根本分不出来。
该由这种不对称来做决定。轮询最坏的情况是数据过时,但你会追上。Webhook 最坏的情况是数据缺失,而你永远不会知道。
连带的成本也由此而来。用了 Webhook,重试逻辑就归你管了,因为平台的重试窗口是有限的,再大方的平台三天后也会放弃。幂等性归你管,因为至少一次(at-least-once)投递让重复成了必然。顺序保护归你管,因为重试会让事件 #2 赶在事件 #1 之前到达。签名验证归你管,因为你的端点是公开的,谁都能向它发 POST。你还要维护一个始终在线的公网 HTTPS 端点,这和“我们的应用一般都在线”是两种不同的可用性要求。
单独看,这些都不难。合在一起,它们就是“直接用 Webhook 不就行了”最后变成两周工程的原因,而且在你以为完工的时候,它其实还没完。
轮询真正适合的场景
遇到下面这些情况,别被效率上的理由劝退。
最明显的一种是你并不需要低时延。每晚一次的会计同步、库存对账、CRM 数据补全任务:如果 5 分钟的延迟没人会注意到,你就是在为一个对谁都没有价值的特性付出实实在在的复杂度。
然后是根本没有地方接收。桌面应用、VPN 后面的内部工具、跑在某个人笔记本上的任务。用内网穿透把 Webhook 引到 localhost 是一种开发手段,而不是生产架构;这条边界在哪里,我们在为什么 ngrok 不够用里写过。
团队最常判断错的一种,是正确性比时效更重要、而截止日期只剩一周的时候。一个你能想清楚的基于游标的轮询,胜过一条只做了一半的 Webhook 管道。
量小的时候也是一样。每 5 分钟轮询一次,一天是 288 个请求,任何限流都察觉不到,运维上的简单还是白送的。平台自己的 Webhook 不靠谱时也是如此:如果它的投递不可靠、事件日志又单薄,轮询它的 API 比信任它的发送端更可靠。
什么时候 Webhook 值得这份复杂度
有人在等的时候,Webhook 的复杂度就值了。支付确认、部署状态、一条聊天消息。这时时延能否压到一秒以下很关键,而要靠轮询快到看起来像实时,就得不停地猛刷 API。
事件稀少又不可预测时,Webhook 也会赢。为一天只发生两次的事情去轮询,意味着大约 99.9% 的请求什么都拿不到,而这正是 Webhook 设计要解决的情况。另一个极端是,量大到轮询根本不可行:一万家店铺、每家都要求 30 秒的新鲜度,一天就是 28,800,000 个请求,远在达到这个数之前你就会撞上限流。
最后一种是长时间运行的任务。视频转码、模型推理、批量导出。平台知道它什么时候完成,你只能猜,所以 AI 和媒体类 API 都在大力推 Webhook。见面向 AI 智能体(AI Agent)的 Webhook。
混合方案:反正你最后也会做成这样
几乎每个成熟的集成最后都会走到这一步,主动走过来,比出了事故之后再走过来便宜。Webhook 负责时效;定期的对账轮询负责正确性。
// Fast path: webhook, near-real-time
export async function POST(req: Request) {
const event = await verify(req);
await upsertEvent(event); // idempotent by provider event id
return new Response(null, { status: 200 });
}
// Safety net: every 15 minutes, ask what we should have
async function reconcile() {
const now = new Date(); // before listing, so nothing updated mid-sweep is skipped
const since = await getLastReconciledAt();
for await (const obj of provider.list({ updated_since: since })) {
await upsertEvent(obj); // same idempotent path, no-op if webhook won
}
await setLastReconciledAt(now);
}
对账任务的代价是一个定时函数,复用的是你已经写好的代码。换来的是:Webhook 最糟糕的特性,也就是无声的永久丢失,变成了“最多晚 15 分钟”。这是完全不同的风险。最坏的情况甚至不是漏掉一笔付款(总会有人发现),而是漏掉一个 access.revoked,这个没人会发现。
让它成立的前提是:两条路径都经过同一个幂等的写入函数。如果 Webhook 已经处理过,对账就什么也不做。如果没处理,对账就把它补上。只有按平台的事件 ID 去重,而不是按你自己的接收时间去重,这一点才成立。具体模式见同一个 Webhook 收到了两次。
然后给重叠的部分加上度量。统计对账有多少次找到了 Webhook 漏掉的东西。这个数字就是你的 Webhook 管道真实的丢失率,也是你唯一能得到的诚实度量。如果它连续一个月为零,你的 Webhook 是健康的。如果它突然飙升,就说明发生过一次你原本永远发现不了的宕机。
被遗忘的中间地带
轮询不一定是笨办法。
基于游标的轮询,也就是用 ?since=<cursor>,而不是“全量拉取再做比对”,成本低得多,而且从不漏掉任何一条,因为只有处理成功后游标才会前进。
条件请求,也就是带 ETag 的 If-None-Match 或 If-Modified-Since,会返回一个没有响应体的 304。很多平台不把这类请求计入你的限流额度。你可以很激进地轮询,几乎不花什么成本。
长轮询能给你接近 Webhook 的时延,同时保留拉取模式的恢复能力。它比较少见,但只要平台提供,往往能同时拿到两边的好处。
如果你因为按固定间隔全量拉取的成本而否定了轮询,那你拿来比较的可能是轮询最差的那个版本。
AnyHook 能做什么
Webhook 之所以会无声地失败,是因为没有人记录本该到达的是什么。AnyHook 就是这份记录。
平台向 in.anyhook.net/you/app 发送 POST。每个事件都会在尝试投递之前持久化,所以:
- 缺口变得可见。投递失败会被计数、展示出来,并在连续失败 1 次、5 次和 20 次时告警,“无声”二字就不再成立
- 恢复靠的是重放,而不是对账任务。选中你的服务器宕机的那段时间,从日志里重放。重放失败的事件不计入你的月度配额,不过有每日上限,并且会消耗你的计算额度
- 基础设施的代价由我们一次付清:指数退避重试、持久化日志,以及统一的
AnyHook-Signature请求头,而不必为每个平台各写一套验证方案
这并不意味着就不需要对账轮询了。如果平台根本没发出这个事件,任何转发服务都变不出来。但它把故障模式从“无声且永久”缩小成了“有日志、有告警、可重放”,而这正是混合方案为你换来的大部分东西。
想在注册任何东西之前先看看效果,一条命令就能返回一个可用的接收 URL,不需要账号:
curl -X POST https://anyhook.net/api/v1/quickstart
响应里有一个 inbound_url,用来填到平台那里;一个 api_key,用来通过 API 读取事件日志;还有一个 claim_url。这个端点是临时的:除非你打开认领 URL 并登录,否则 7 天后它和它的事件都会被删除;认领之后 URL 保持不变。
总结
按故障模式来选。轮询慢,但能自愈;Webhook 快,但会悄悄丢东西。如果时延既不影响人,也不影响限流,那轮询就不是新手选项,而是最坏情况更好的那个。如果你确实在生产环境里跑 Webhook,就在需要之前加上对账轮询,因为它产出的指标,是你知道自己实际丢了多少事件的唯一办法。