Webhook2026年4月18日6 分钟阅读

服务器宕机期间的 Webhook 怎么重放

服务器宕机了三个小时。Stripe、GitHub 和 Shopify 一直在发 Webhook。漏掉的事件怎么重放,有转发服务和没有转发服务时各怎么做。

日志从你的源站开始源站宕机 · 什么都没写入你只能重放源站收到了的事件日志从边缘节点开始源站宕机 · 事件照样持久化选中时间段 → 重放
重放能做到什么程度,取决于你的日志从哪里开始记录。

你的数据库在凌晨 2 点挂了。等你醒来修好,三个小时的 Webhook 已经发出,也都被你的服务器拒掉了。现在你盯着 Slack,琢磨有多少笔 Stripe 付款没有履约,有多少次 GitHub push 没有触发该触发的 CI 任务。

这篇文章是恢复漏掉的 Webhook 的实操手册,按平台逐个讲,最后讲一种模式,让这件事变成点一下就能解决的问题,而不是耗掉一个周日下午。

TL;DR

  • Stripe:在控制台里逐个事件手动重发,或者通过 Events API 写脚本。没有批量操作的界面。
  • GitHub:每次投递一个重新传递按钮。同样只能一次一个,除非你用 Redelivery API 写脚本。
  • Shopify:在 4 小时内重试 8 次,之后事件就没了,你只能重新查询 Orders API 来对账。
  • 一键重放需要在投递之前、在上游就记录日志,而这正是 Webhook 转发服务的全部设计。

Stripe

Stripe 允许你在控制台里手动重发 Webhook 事件:

  1. 在 Stripe 控制台进入 Developers → Events(开发者 → 事件)
  2. 点击你要重发的事件
  3. 滚动到 Webhook attempts(Webhook 投递尝试),点击 Resend(重新发送)

这只能一次处理一个事件,而且只在事件创建后的 15 天内有效。如果你漏掉了 300 个事件,就得点 300 次按钮。Stripe CLI 有一个命令(stripe events resend <event_id>),但你仍然需要知道每个事件的 ID。

批量重放 Stripe 事件

Stripe 官方没有批量重发的界面。变通办法有:

  • 写一个脚本,用 Stripe API 列出宕机时间段内的事件,按状态过滤,再自己把每个事件 POST 回你的端点。你是在用事件对象重新构造 Webhook,形态和原来的并不完全一样。
  • 或者,如果你在宕机点的上游记录了 Webhook,就从你自己的日志里重放

生产团队最终都会自建上游日志这套方案。而这也正是 Webhook 转发服务替你做的事。

GitHub

GitHub 为每次投递提供一个重新传递按钮,位置在仓库的 Settings → Webhooks →(选择 Webhook)→ Recent Deliveries(最近交付)。在每一条旁边点击 Redeliver(重新传递)。

同样的问题:一次只能一个。GitHub 只在 3 天内允许重新传递,大批量重新传递仍然意味着写脚本去调 Redelivery API。而且那份投递记录就是你仅有的东西,因为 GitHub 从不自动重试失败的投递。

Shopify

Shopify 会在四小时的窗口内重试 8 次,然后放弃。之后,唯一的恢复办法是重新查询 Shopify API,找回你漏掉的内容。Webhook 本身已经没了。

这一家尤其痛苦,因为 Shopify 的恢复窗口是三大平台里最短的,而且短得多。如果你的服务器周五晚上宕机,直到周一才发现,你就得用 Orders API 做一次全量对账。

通用流程

不管是哪个平台,没有转发服务时,恢复流程大致如下:

  1. 确定宕机时间段。你的服务器从什么时候开始不再返回 2xx?
  2. 拉取受影响的事件,通常每个平台都要做一次分页 API 调用
  3. 把每个事件 POST 到你自己的端点重新触发,或者重新查询权威数据源,手动同步状态
  4. 去重,因为你的处理程序必须幂等,而且有些事件可能已经部分投递

这要花好几个小时。而且你只有一次机会:如果错过了重试窗口,事件就没了。

一键版本

AnyHook 存在的理由,就是把这件事变成点一下。当你把 Webhook 指向 in.anyhook.net/you/app 时:

  • 每个事件都在投递之前持久化,并做静态加密
  • 如果你的服务器返回 5xx,AnyHook 会按指数退避重试(投递次数:Free=3,Pro=5,Scale=10)
  • 如果你的服务器宕机时间长到重试耗尽,事件会被标记为失败,但请求体仍然在你的日志里
  • 打开 App 页面,在失败事件横幅里选择时间范围(最近一小时、最近 24 小时,或整个重放窗口),点击 Replay(重放)。它每次发送 100 个,最早的优先。就这么简单。

重放失败的事件不计入你的月度配额,但会消耗你套餐的计算额度。每个套餐都有每日重放上限(Free 100 个,Pro 500 个,Scale 1,000 个),而因超出配额被暂存的事件,重放时按新事件计算。重放会带着新的签名重新发送到你的端点,同时保留接收时的原始请求体和请求头,所以你的处理程序看到的事件,和平台发出时完全相同。

对应的 API 只需一个 POST:

curl -X POST https://anyhook.net/api/v1/apps/stripe-prod/replay-failed \
  -H "Authorization: Bearer $ANYHOOK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"since": "2026-04-18T02:00:00Z", "limit": 100}'

一次调用,最多重放 100 个事件,不用在控制台里点来点去。

设计上的启示

让重放变得低成本的,是在投递之前、在边缘节点完成的持久化。如果你的 Webhook 日志从源站开始记录,你只能重放源站收到过的东西。如果日志从网络边缘开始记录,赶在源站有机会出故障之前,那么任何到达过的东西你都能重放。

这些尝试之间的重试计划本身就是一个设计问题。AnyHook 用的是不加随机抖动(jitter)的固定阶梯,Webhook 重试的指数退避与随机抖动讲了为什么这样的阶梯会让所有客户端在宕机后挤到同一秒,以及为什么低成本的重放终究胜过激进的重试。如果你发现自己经常需要重放,更早该问的问题是,这个边界到底该不该用 Webhook,见 Webhook 和轮询。

日志从你的源站开始源站宕机 · 什么都没写入你只能重放源站收到了的事件日志从边缘节点开始源站宕机 · 事件照样持久化选中时间段 → 重放

把日志挪到上游,让重放幂等,你最糟糕的一次 Webhook 宕机,就会从耗掉一个周末变成一分钟的恢复。

有两篇后续文章,值得在你用得上之前先读。“让重放幂等”是承重的那一半,一旦重试并发到达,它比一张去重表要难:同一个 Webhook 收到了两次。如果 Stripe 已经放弃并禁用了你的端点,它就不再重试了,你只能在 30 天的窗口内用 Events API 回填:Stripe 禁用了你的 Webhook 端点。

试试看:anyhook.net。

全部文章2026年4月18日 · 6 分钟

别再丢 Webhook 了。

只改一个 URL。获得重试、事件日志和一键重放。也可以先亲眼看看:注册后跑一次 30 秒的故障演练,看一个事件如何挺过你的服务器宕机。不需要服务器。