教程:用 Webhook 接收异步事件——从签名校验到幂等重试
为了拿到一个任务结果,每三秒轮询一次接口,既慢又烧额度。换成事件订阅后状态实时推过来——但签名不校验,等于把门敞开。
为什么要换成事件订阅
轮询有三个绕不过的代价:延迟取决于轮询间隔,间隔短则请求量白白翻倍,间隔长则用户体验变差。更麻烦的是,绝大多数轮询请求的结果都是「还没好」。
事件订阅把关系反过来:状态一变,服务端主动推给你。请求量与真实事件数成正比,而不是与等待时间成正比。
接入四步
第一步,在控制台登记回调地址并订阅关心的事件类型,只订阅真正会处理的——订阅越少,后续排查越简单。
第二步,实现接收端点:先返回成功,再异步处理。回调方通常有较短的超时,把重活放到接收线程里做,会被判定失败并重复投递。
第三步,校验签名:用约定的密钥对原始请求体计算摘要,与请求头中的签名比对,比对通过才认可这次投递。
第四步,按事件 ID 做幂等:先查是否已处理过,处理过就直接返回成功;没处理过才落库执行。
- 登记回调 + 只订阅必要事件
- 先应答 200,再异步处理
- 用原始请求体校验签名(不要用反序列化后的对象)
- 按事件 ID 幂等,重复投递直接返回成功
签名校验的两个坑
坑一:拿反序列化之后再重新序列化的字符串去算签名。字段顺序、空格、转义都可能变了,摘要自然对不上——必须用收到的原始字节。
坑二:只比对签名是否相等,却没做时间窗校验。缺少时间戳校验意味着一条旧请求可以被无限重放。正确做法是校验签名 + 拒绝超出时间窗的请求,两者缺一不可。
幂等与重试怎么配合
回调方的策略通常是「失败就重试,间隔递增」。这意味着你的端点必须能安全地被重复调用:把事件 ID 作为唯一键,处理前先查、处理后标记,重复投递在查询这一步就被挡掉。
处理失败时不要吞掉异常直接返回成功,否则事件永久丢失;应当返回失败,让重试机制接管,同时记录失败次数,超过阈值转人工。
验证方式
造三类请求各发一次:合法签名的新事件(应处理成功)、合法签名但重复的事件 ID(应直接返回成功且不重复执行)、签名错误的请求(应拒绝且不留副作用)。再断网一次让回调方重试,确认重试后数据仍然只有一条。
事件订阅的复杂度不在接收,而在「收到之后怎么保证不重不漏」。把幂等和签名做对,剩下都是体力活。