跳转到正文
OmniChannels.ai

WhatsApp Webhook指南:事件、安全与重试

WhatsApp Webhook的工作方式:消息与状态事件、可靠的处理程序、Meta签名校验、重试机制、幂等处理,以及事件丢失时的恢复办法,帮助开发者稳定接收送达状态。

更新于 10 分钟阅读

作者: OmniChannels.ai Editorial Team · 审阅: BroadNet Messaging Solutions Team

集成

简短回答

WhatsApp Webhook是您服务器上用于接收事件的HTTPS端点:包括您所发消息的送达状态,以及客户发给您的消息。在OmniChannels.ai中,状态以SUBMITTED、SENT、DELIVERED、READ、FAILED或REJECTED的形式到达。请保存原始请求体,快速返回2xx,按消息ID去重并异步处理。Meta会为自己的Webhook签名;我们的文档没有定义签名请求头,所以请在传输层和应用层保护您的端点。

本页内容
  1. 什么是WhatsApp Webhook,为什么要使用?
  2. 我会收到哪些事件和状态?
  3. 如何设置端点?
  4. 负载是什么样的?
  5. 如何编写可靠的处理程序?
  6. 如何保护WhatsApp Webhook的安全?
  7. 投递失败时会怎样?
  8. 如何恢复丢失的事件?
  9. 实战示例:一家零售商的通知管道
  10. 常见错误有哪些?
  11. OmniChannels.ai 如何提供帮助

要点速览

  • Webhook把状态和收到的消息推送到您的HTTPS端点,让您无需轮询;WhatsApp的状态被统一为SUBMITTED、SENT、DELIVERED、READ、FAILED和REJECTED。
  • 可靠的处理程序按顺序做三件事:保存原始事件,返回2xx,然后异步且幂等地处理。
  • 状态可能乱序或重复到达,所以请为其排序,绝不要让迟到的SENT覆盖已存储的DELIVERED。
  • Meta会对发往应用的Webhook使用X-Hub-Signature-256请求头签名,但OmniChannels.ai的API规范没有定义Webhook签名,所以请使用HTTPS、不可猜测的路径和请求体校验。
  • 如果错过了事件,请通过bulk-insights端点按批次读取WhatsApp状态,而不是重发消息。

本指南面向需要可靠接收WhatsApp事件的工程师:包括所发消息的送达状态,以及客户发来的消息。内容说明Webhook如何工作、处理程序里要构建什么、Meta的Webhook与我们的Webhook在安全方面有何不同,以及事件丢失时该怎么办。

什么是WhatsApp Webhook,为什么要使用?

WhatsApp Webhook是HTTP回调。当一条消息的状态发生变化,或客户给您写信时,平台会向您的端点发送带有JSON请求体的POST。您无需轮询,事件一发生您就能看到。

有两类事件很重要:

  • 您所发消息的**状态事件:**已排队、已发送、已送达、已读、失败。
  • 客户发来的**收到的消息,**它们会开启或延长24小时客户服务窗口。请参阅我们的窗口指南。

发送调用只告诉您消息已排队。Webhook才是您得知每位收件人发生了什么的地方。

我会收到哪些事件和状态?

在OmniChannels.ai上,每个渠道报告的都是同一套统一的状态,所以一个处理程序就可以服务WhatsApp、短信和其他渠道。

状态 含义 是否为最终状态
SUBMITTED 已被平台接受并排队 否
SENT 已交给渠道 否
DELIVERED 已在收件人的设备上确认 通常是;之后可能出现READ
READ 已被收件人打开(在渠道报告已读的情况下) 是
FAILED 未送达;事件在有原因时会附带原因 是
REJECTED 被拒绝,例如内容被封或发件人未注册 是

请把状态视为一个可能乱序到达的推进过程,并预期随着时间推移会出现新的值。不要因为遇到不认识的状态而失败。请参阅消息送达状态详解。

如何设置端点?

开放一个HTTPS端点,把URL交给您的服务商,然后用您自己的号码测试。

  1. 创建一个接受带JSON请求体的POST的端点,例如https://api.example.com/webhooks/omnichannels。
  2. 把URL发给我们的团队或您的账户管理员,让其在您的账户上配置。转售商可以为每个子账户设置单独的Webhook。
  3. 向您自己的号码发送一条测试消息,确认事件已到达。

开发期间,ngrok之类的隧道工具可以为您本地的服务器提供一个公开的HTTPS URL。如果您还把应用直接连接到Meta,那么Meta的端点验证是单独的一步:一个带有验证token和hub.challenge值的GET请求,您以200并带上该challenge回应,详见Meta的Webhook端点文档。

负载是什么样的?

因渠道而异。API规范没有定义单一的Webhook结构,所以请先记录原始请求体,再根据您账户上的真实事件映射所需字段。示例展示的是一个送达事件所携带信息的类型,仅供说明。

{
  "messageId": "2603121407537573731",
  "channel": "SMS",
  "destination": "447700900123",
  "status": "DELIVERED",
  "timestamp": "2026-10-10T09:15:42Z",
  "reason": null
}

在核对真实事件之前,不要依据这些字段名编写代码。请向您的开通工程师索取每个渠道的示例负载,并将其保存为测试夹具。更全面的文档请参阅Webhook参考。

如何编写可靠的处理程序?

按顺序做三件事:保存原始事件,以2xx确认,然后异步处理。下面的处理程序遵循我们文档中的模式。

import express from "express";

const app = express();

// 捕获原始请求体,以便按收到的原样存储。
app.post(
  "/webhooks/omnichannels",
  express.json({ verify: (req, _res, buf) => { req.rawBody = buf.toString("utf8"); } }),
  async (req, res) => {
    try {
      await db.webhookEvents.insert({ receivedAt: new Date(), body: req.rawBody });
    } catch (err) {
      return res.sendStatus(500); // 存储失败:让事件被重试
    }
    res.sendStatus(200);          // 尽快确认
    queue.publish("status-events", req.body); // 把真正的工作放到别处
  },
);

// 工作进程:幂等处理
async function processEvent(event) {
  const id = event.messageId ?? event.message_id ?? event.id; // 按渠道映射
  const status = event.status;
  const rank = { SUBMITTED: 1, SENT: 2, DELIVERED: 3, READ: 4, FAILED: 4, REJECTED: 4 };

  const current = await db.messages.findOne({ providerId: id });
  if (!current) return; // 未知消息:记录日志并调查
  if ((rank[status] ?? 0) <= (rank[current.status] ?? 0)) return; // 重复或过时

  await db.messages.update({ providerId: id }, { status, updatedAt: new Date() });
}
from datetime import datetime, timezone
from flask import Flask, request

app = Flask(__name__)

RANK = {"SUBMITTED": 1, "SENT": 2, "DELIVERED": 3, "READ": 4, "FAILED": 4, "REJECTED": 4}


@app.post("/webhooks/omnichannels")
def omnichannels_webhook():
    raw = request.get_data(as_text=True)
    try:
        db.webhook_events.insert({"received_at": datetime.now(timezone.utc), "body": raw})
    except Exception:
        return "", 500           # 存储失败:让事件被重试
    queue.publish("status-events", raw)  # 异步处理
    return "", 200               # 尽快确认

db和queue对象是您自己的存储和任务队列。rank映射使处理具备幂等性:重复或过时的事件不是推进,因此会被忽略。

可靠处理程序的规则

  • **在几秒钟之内响应。**响应之前不要做繁重的工作。
  • **只有在事件已保存之后才返回2xx。**如果存储失败,请返回5xx。
  • **按消息ID去重。**同一状态可能多次到达。
  • **绝不要在处理程序中重发消息。**请在检查状态之后,于单独的任务中决定重试。
  • **保留原始请求体,**用于排查问题,以及在修复后重放事件。

如何保护WhatsApp Webhook的安全?

这取决于谁向您的端点发送。Meta会为发送到您在Meta注册的应用的Webhook签名。OmniChannels.ai的API规范没有定义Webhook签名请求头,所以不要等待它。

Meta到您自己的应用 OmniChannels.ai到您的端点
签名 按Meta文档,使用X-Hub-Signature-256请求头 API规范中未定义
校验 使用应用密钥对原始负载计算HMAC-SHA256,并与请求头比对 不适用
您的控制措施 对每个POST检查签名 带有效证书的HTTPS、不可猜测的路径、严格的请求体校验

对于Meta的方案,Meta的文档说明,应使用您的应用密钥对JSON负载生成HMAC-SHA256哈希,并与X-Hub-Signature-256请求头中sha256=之后的值比对。HMAC本身由RFC 2104定义。请对原始字节计算,而不是对重新序列化的对象计算,并以恒定时间比较。

对于我们的端点:

  • 仅使用HTTPS,并配有效证书。
  • 使用较长且不可猜测的路径,泄露时请轮换。
  • 校验请求体的结构,拒绝任何意外内容。
  • 把事件视为通知。在执行退款或欺诈锁定等不可逆操作之前,请通过轮询调用确认状态。

投递失败时会怎样?

重试取决于发送方。Meta的文档称,它会以递减的频率重试失败的投递,最长7天,这可能产生重复通知。这是即使您很少看到重复,也应具备幂等性的理由。对于来自我们平台的事件,请与您的开通工程师确认重试行为,而不要假定它与Meta相同。

无论哪种情况,既要防重复,也要防丢失:您的端点可能在部署期间宕机,防火墙规则也可能悄悄丢弃请求。

如何恢复丢失的事件?

轮询批次,而不是重发消息。

需求 调用 说明
读取每个收件人的WhatsApp状态 GET /whatsapp/message/bulk-insights 按status或destination筛选一个批次;用page和size分页
重新推送短信送达回执 POST /sms/resendDlr 按startDate、endDate、senderId和messageStatus筛选
curl "https://api.broadnet.me/whatsapp/message/bulk-insights?batch_Id=2609091405000000001&page=0&size=100" \
  -H "Authorization: Bearer $BROADNET_TOKEN"

batch_Id是发送调用返回的ID,page从零开始。请运行一个夜间对账任务,读取前一天的批次并填补任何缺口。

故障排查清单

  1. URL是否配置在了正确的账户或子账户上?
  2. 端点能否通过带有效证书的HTTPS访问?
  3. 它是否迅速返回2xx,还是超时?
  4. 是否有防火墙或WAF规则在拦截请求?
  5. 您是否在解析之前记录了原始请求体,让解析错误无法掩盖事件?

实战示例:一家零售商的通知管道

“Souq Rafiq”(虚构)是阿联酋的一家零售商,通过WhatsApp发送订单更新,并希望在订单工具中获得准确的状态。

  1. 订单通过sendbulk发送,batchId存储在订单上。请参阅通过API发送WhatsApp消息。
  2. Webhook端点把每个事件写入一张表,然后返回200。
  3. 工作进程为状态排序,并更新订单的消息状态。
  4. FAILED事件会为客服创建一个带原因的任务,并按渠道回退考虑短信回退。
  5. 收到的客户消息被转到共享收件箱,让客服可以在窗口内回复。
  6. 夜间任务对昨天的批次调用bulk-insights,并纠正任何遗漏。

常见错误有哪些?

  • **响应之前做繁重的工作,**导致超时和重复投递。
  • **先确认再存储,**导致存储失败时事件丢失。
  • **相信顺序。**迟到的SENT覆盖了DELIVERED。
  • **依据示意性负载编写代码,**而不是依据真实事件。
  • **检查签名之前重新序列化JSON,**会改变字节。
  • **在处理程序中重发,**可能造成循环。
  • **局限:**本指南描述的是我们文档中记载的行为。您账户的重试策略和负载字段,应在开通期间确认。

OmniChannels.ai 如何提供帮助

OmniChannels.ai把WhatsApp、短信、RCS和其他渠道的送达状态和收到的消息,以一种统一的格式推送到您的Webhook,并为转售商提供按子账户划分的单独Webhook。BroadNet是Meta官方的Business Solution Provider,因此Meta自身的Webhook要求已为您处理,您只需使用我们的API。请阅读Webhook参考和WhatsApp API文档,或查看事件在共享收件箱中的呈现方式。

常见问题

什么是WhatsApp Webhook?

WhatsApp Webhook是您服务器上的一个URL,每当发生某些事情(如消息被送达、已读或失败,或客户给您发消息)时,平台就会以HTTP POST调用它。它取代了轮询。您开放一个HTTPS端点,与服务商共享,并处理它接收到的JSON事件。

如何校验WhatsApp Webhook的签名?

对于Meta直接发送到您自己应用的Webhook,Meta的文档说明,应使用您的应用密钥对原始负载计算HMAC-SHA256,并与X-Hub-Signature-256请求头比对。对于OmniChannels.ai的Webhook,API规范没有定义签名请求头,所以请依靠HTTPS、不可猜测的路径和请求体校验。

如果我的Webhook端点宕机了会怎样?

Meta的文档称,它会以递减的频率重试失败的投递,最长7天,这可能产生重复。对于来自我们平台的事件,请与您的开通工程师确认重试行为,并使用bulk-insights端点读取某个批次中每个收件人的WhatsApp状态。对于短信,resendDlr端点可以重新推送回执。

如何处理重复的Webhook事件?

让处理具备幂等性。按消息ID查找消息,将传入状态的等级与已存储的比较,并忽略那些不是推进的事件。同一事件应用两次,效果必须与一次相同。请保存每个事件的原始请求体,以便在修复错误后重放。

我的端点必须多快响应?

几秒钟之内。保存原始事件,返回2xx,并把真正的工作放到队列或工作进程中。在响应之前,不要调用其他服务、发送消息或运行繁重的查询。如果存储失败,请返回5xx,让发送方可以重试,而不是确认一个您已经丢失的事件。

可以用Webhook自动发送回复吗?

可以,但不要在处理程序内部进行。先确认事件,然后在单独的任务中决定回复。在24小时客户服务窗口内,您可以发送自由格式消息;窗口之外则需要已获批的模板。绝不要在Webhook处理程序本身里重发消息,以免出现循环和重复。

资料来源

  1. Webhook概览——WhatsApp Business平台,Meta for Developers
  2. 创建Webhook端点——WhatsApp Business平台,Meta for Developers
  3. RFC 2104:HMAC:用于消息认证的密钥哈希