轮询状态是获取验证码最简单的方式:发请求、查响应,若为空则几秒后再试一次。这种简单是有代价的——每次多余请求都会消耗 API 调用限额,延迟依旧无法确定,因为验证码可能刚查完就已生成,却要等到下一轮才被取走。Webhook 运作方式完全不同:事件一旦发生,服务端会主动通知你。下面说明切换到 webhook 带来什么变化、接收端需要准备什么、如何辨别请求真伪,以及什么时候轮询仍然更合适。

推送与轮询:webhook 到底带来什么

轮询模式下,客户端每隔几秒就要请求一次状态接口,在验证码到达前,几乎每次响应都是「暂无新内容」。哪怕只是十几个并发激活,每分钟也会产生成百上千次无效调用,触及限额的同时,并未缩短验证码生成与应用感知之间的真实间隔。验证码本身是什么,可参阅什么是 OTP 验证码一文。

Webhook 则反过来:事件一旦准备好,发送方服务器会主动向你的地址发起请求。延迟压缩到单次 HTTP 请求的传输耗时,调用次数也从大量轮询降到每次激活仅需一次。

接收端需要准备什么

第一个条件是公网可达的地址。接收端必须能通过 HTTPS 从外部访问——使用域名或公网 IP,不能是内网地址,发送方与服务器之间也不能存在代理。如果地址无法从公网解析,事件就无处可送——发送方记录的是连接错误,而不是沉默。接收端为何不可达、如何排查,详见回调中断的原因分析一文。

第二个条件是响应速度。处理程序须先接收请求体并立即返回 2xx 状态码,而不是等业务逻辑跑完——解析文本、用正则提取验证码、写入数据库都应放入队列,由独立进程异步执行。若处理逻辑同步阻塞到响应之后,一旦数据库或第三方服务出现延迟就会触发超时,发送方会把此次投递标记为失败,尽管事件其实已经到达。

请求签名:如何确认 webhook 是真实的

公开的接收地址任何人都可能找到,或者从截获的流量中窥见。没有身份校验的话,接口会接受任意内容的请求体——只需发一个带着伪造「验证码」的 POST 请求,处理程序就会把它当成真实事件处理。

签名机制正是用来堵住这个漏洞的:发送方用密钥对请求体做哈希运算,把结果放进请求头,接收端用同一密钥重新计算哈希并比对数值。两者一致,既证明来源可信,也证明请求体在传输途中未被篡改。密钥只应保存在服务端,绝不能出现在客户端代码中;一旦怀疑泄露,应在后台立即轮换,并同步更新接收端的校验逻辑。

重复投递是常态,而非故障

发送方永远无法百分之百确认 2xx 响应意味着「事件已保存」,而不是「服务器回复了,但处理逻辑随后崩溃」。因此大多数系统都会按计划重试投递——先等几秒,再等一分钟,如此反复,直到收到确认或用尽次数。结果是,同一个验证码、同一个事件 ID,可能被送达两次,甚至三次。

处理程序必须具备幂等性:写入前先检查该激活或事件 ID 对应的验证码是否已存在,若已存在,重复请求只需返回 200,不再重新创建。发送方的重试逻辑,与你自己调用第三方 API 时应有的重试逻辑本质相同,共通原则见API 错误:如何处理与重试一文。若接收端在整个重试窗口内始终不可达,事件不会彻底丢失,唯一的前提是发送方保留了未送达事件的队列,你可通过单独的状态查询手动取回遗漏的事件。

什么时候轮询仍然更合适

如果任务没有长期的公网地址——没有域名的测试环境、NAT 之后运行的一次性脚本、第二天就下线的短期试点——专门搭建 webhook 并不划算。间隔几秒的轮询足以覆盖一次性或低频任务,无需接收端基础设施和签名校验。

第二种情形是真正的单次请求:一个验证码对应一次激活,没有持续事件流,webhook 带来的收益覆盖不了搭建公网地址和处理队列的成本。而在常规或并发接收场景下,这个权衡会反过来:轮询消耗限额的速度会比它节省的集成时间更快。

常见问题

可以让多个服务共用同一个 webhook 地址吗?

技术上可以,只要处理程序能通过请求体中的服务或激活标识区分不同事件。更实用的做法是把不同来源拆分到同一域名下的不同路径——日志更易读,且可以只轮换某一来源的签名,不影响其余来源。

如果服务商不支持重试投递,该怎么办?

可并行设置状态轮询作为保险,应对接收端恰好在唯一一次推送时不可达的情况。轮询频率不必高,几分钟一次即可捕获遗漏事件,且不至于让轮询变成主要通道。

上线前如何确认签名校验逻辑正确?

发送一个请求体被故意破坏或签名头错误的测试事件,确认处理程序会在写入前将其拒绝。随后用正确签名和同一事件 ID 连续发送两次——第二次调用必须返回 200,且不能重复创建记录。

事件格式、签名请求头、请求体结构以及重试参数等 webhook 接收验证码的完整说明,见API 文档