原创

Webhook 实战

paste-image-1784166864662.png

做 SaaS 时,你很快会遇到 Webhook。支付成功、订阅取消、邮件退信、文件处理完成、第三方数据同步,很多外部系统都会通过 Webhook 通知你。

Webhook 看起来只是“对方请求你的一个接口”,但真正上线后,坑很多:重复通知、签名校验、超时、乱序、重试、数据一致性、日志排查。

如果你把 Webhook 当普通接口写,很容易在支付、订单、权限这类关键流程里出问题。

Webhook 本质是外部系统主动通知你

普通 API 通常是你主动请求对方,比如你调用支付平台创建订单。

Webhook 则相反,是对方在事件发生后主动请求你的接口。比如用户付款成功后,支付平台会请求你的 /api/webhooks/payment,告诉你这笔订单已经完成。

这意味着 Webhook 有几个特点:请求来自外部系统,发生时间不完全由你控制,可能重复发送,可能延迟到达,也可能因为你的服务异常而重试。

所以 Webhook 的设计重点,不是“能收到”,而是“收到后能安全、稳定、可追踪地处理”。

第一步:为每个来源设计独立入口

不要把所有 Webhook 都塞进一个通用接口。

支付平台、邮件服务、文件服务、AI 服务、数据同步服务,事件结构、签名方式、重试策略都不一样。最好为不同来源设计独立入口。

比如:

POST /api/webhooks/stripe
POST /api/webhooks/paypal
POST /api/webhooks/email
POST /api/webhooks/storage

独立入口让签名校验、日志记录、错误排查和权限隔离都更清楚。

第二步:先验签,再处理

Webhook 最大的安全问题,是不能相信任何请求。

攻击者可能伪造支付成功、伪造订阅状态、伪造文件处理完成。如果你只看请求体里的数据,不做签名校验,就可能被恶意调用。

大多数成熟服务都会提供签名校验机制。你需要用平台提供的 secret,对原始请求体和请求头做校验,确认请求确实来自对方。

注意很多 Webhook 验签要求使用原始请求体。如果你的框架提前解析了 body,可能会导致签名校验失败。这个细节要在接入时特别确认。

第三步:事件要先落库

Webhook 到达后,不要只在内存里处理,也不要处理完就忘。

建议先把事件 ID、来源、事件类型、原始 payload、接收时间、处理状态存进数据库。这样后面出问题时,你能查到到底收到了什么、什么时候收到、处理结果是什么。

一个简单事件表可以包含:

id
source
event_id
event_type
payload
status
received_at
processed_at
error_message

事件落库,是 Webhook 可追踪的基础。

第四步:必须做幂等

Webhook 可能重复发送,这是正常行为。

比如你的接口超时,对方没有收到成功响应,就会重试;网络波动,也可能导致重复;有些平台本来就保证“至少送达一次”,而不是“只送达一次”。

所以处理 Webhook 时,一定不能假设同一个事件只会来一次。

最常见的做法,是用对方提供的 event_id 做唯一约束。如果同一个事件已经处理过,直接返回成功,不要重复创建订单、重复发邮件、重复增加额度。

支付和订阅类 Webhook 尤其要小心。

第五步:快速返回成功

Webhook 接口不适合做很耗时的业务处理。

如果你在接口里同步生成报表、发送多封邮件、调用多个第三方服务,很容易超时。一旦超时,对方可能会重试,造成重复处理压力。

更稳妥的方式是:验签通过后,事件落库,放入队列或标记待处理,然后尽快返回成功。真正耗时的业务放到后台任务里处理。

Webhook 接口负责接收和确认,业务处理器负责执行和重试。

第六步:按事件类型分发处理

一个来源可能有很多事件类型。

比如支付系统可能有 checkout.completedinvoice.paidsubscription.updatedsubscription.canceled。邮件系统可能有 deliveredbouncedcomplained

不要在一个巨大的 if-else 里处理所有事件。可以按事件类型拆成独立 handler。

checkout.completed -> 激活订单
invoice.paid -> 续费成功
subscription.canceled -> 取消订阅
email.bounced -> 标记邮箱异常

这样每个事件逻辑更清楚,也更容易测试。

第七步:处理乱序和状态覆盖

Webhook 不一定按你期待的顺序到达。

比如订阅先收到取消事件,后收到更新事件;订单状态先收到成功,后收到旧状态;用户权限已经变更,晚到的事件又把它改回去。

处理这类问题时,不要只根据“最新收到的事件”覆盖状态,而要看事件时间、业务状态机和当前状态。

对于关键业务,可以先查询平台当前真实状态,再决定本地如何更新。

Webhook 是通知,不一定是最终真相。必要时要回查来源系统。

第八步:日志和告警要做好

Webhook 出问题很难靠用户描述排查。用户只会说“我付费了但没有开通”或“邮件显示失败”。

你需要能快速查到:是否收到事件,签名是否通过,事件是否落库,是否处理成功,失败原因是什么,是否重试过。

至少要记录来源、事件 ID、事件类型、处理耗时、错误信息和关联业务对象。

对支付、订阅、关键数据同步这类 Webhook,失败时应该有告警。

本地调试要准备工具

Webhook 来自外部系统,本地开发时通常无法直接被访问。

你可以使用隧道工具把本地服务暴露给外部,或者使用平台提供的 CLI、测试事件、重放功能。

调试时要关注三件事:请求是否到达本地,签名是否通过,事件处理结果是否符合预期。

不要等上线后才第一次测试 Webhook。Webhook 必须在开发和预发布环境里反复验证。

一个 Webhook 处理流程

可以按下面流程设计:

1. 接收请求
2. 读取原始 body
3. 校验签名
4. 解析事件
5. 用 event_id 去重
6. 事件落库
7. 快速返回成功
8. 后台分发处理
9. 更新业务状态
10. 记录日志和错误

只要这个流程稳定,绝大多数 Webhook 场景都能应对。

写在最后

Webhook 不是普通接口,它是外部系统和你内部业务状态之间的桥。

真正可靠的 Webhook 设计,一定要包含验签、幂等、落库、快速响应、后台处理、日志和重试。尤其涉及支付和订阅时,不要省略这些基础步骤。

下一篇,我们继续聊登录体系:OAuth 登录怎么接

正文到此结束
Loading...