Twilio 如何投递 Webhook

Twilio 的每个超时和重试参数都可以配置,其他主流发送方都不允许这样做。附 HMAC-SHA1 URL 签名和 Node 验证器。

投递行为

取自该平台的官方文档,最近核对于 2026-08-06。平台没有给出数字的地方,这张表也不填。

响应时限15 秒

可配置。rt 读取超时默认 15000 毫秒(最大 15000),ct 连接超时默认 5000 毫秒(最大 10000),tt 总超时默认 15000 毫秒

检测你的处理程序能否及时应答

重试可配置:rc 取 0 到 5,默认 1。rp 指定哪些失败可以重试
放弃平台未公布
出处https://www.twilio.com/docs/usage/webhooks/webhooks-connection-overrides

签名

用一个经测试套件证明的可运行验证器核对,不是照着文档读来的。最近核对于 2026-08-20。

算法HMAC-SHA1
待签名串full URL + form params sorted by key
编码base64
请求头x-twilio-signature
时间容差无强制要求

验证签名

要用原始请求体,逐字节一致,并且在任何 JSON 解析之前。框架一旦重新序列化请求体,本页的每一种方案都会失效。

const crypto = require("node:crypto");

// base64(HMAC-SHA1(auth token, url + form params sorted by key, concatenated))
const sorted = Object.keys(req.body).sort();
const base = fullUrl + sorted.map((k) => k + req.body[k]).join("");
const expected = crypto
  .createHmac("sha1", authToken)
  .update(base)
  .digest("base64");
const received = Buffer.from(req.headers["x-twilio-signature"] ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

签名覆盖完整的公网 URL,包括查询字符串。处理程序前面的 TLS 终止或改写路径的代理,会改变 Twilio 签名时的 URL,签名对不上时看起来和伪造请求一模一样。

容易踩的坑

每个 URL 都可以配置五个投递参数:连接超时、读取超时、总时长、重试次数、重试策略。而几乎所有人用的都是默认值(总共 15 秒,重试 1 次)。旋钮就在那里,没人去拧。

延伸阅读

AnyHook 挡在接收 Twilio Webhook 的端点前面:约 1 秒应答,你的服务器宕机时按自己的计划重试,并且每个事件都可以重放。 只改一个 URL,代码不用动。

工作原理 →