Meta (Instagram, Facebook, Threads) 如何投递 Webhook

Meta 在 36 小时内以逐渐降低的频率重试,之后丢弃事件。讲解 hub.challenge 握手,附签名格式和 Node 验证器。

投递行为

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

响应时限平台未公布
重试36 小时内以逐渐降低的频率重试,之后丢弃。WhatsApp Cloud API 最多重试 7 天
放弃平台未公布
出处https://developers.facebook.com/docs/graph-api/webhooks/getting-started

签名

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

算法HMAC-SHA256
待签名串raw body
编码hex
请求头x-hub-signature-256 = sha256=hex
时间容差无强制要求

验证签名

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

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

// Same scheme as GitHub: "sha256=" + hex HMAC of the raw body, keyed by the
// app secret.
const expected =
  "sha256=" +
  crypto.createHmac("sha256", appSecret).update(rawBody).digest("hex");
const received = Buffer.from(req.headers["x-hub-signature-256"] ?? "");
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);

在这一切之前,Meta 必须先接受你的回调 URL:它会发送一个带 hub.mode=subscribe 的 GET 请求,并要求原样返回 hub.challenge。不返回,就没有订阅。

容易踩的坑

Meta 公布了重试窗口(36 小时,频率逐渐降低,之后丢弃通知),但没有公布单个请求的响应时限,也没有公布停用阈值。平台没有给出数字的地方,本页也不填。

延伸阅读

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

工作原理 →