WhatsApp 消息状态事件
Bird 为入站和出站 WhatsApp 消息记录事件。出站时间线展示发送返回 202 后发生的情况:接受、移交至 WhatsApp、投递、已读或失败。入站时间线记录 Bird 收到消息的时间。
本页介绍如何通过 API 读取该时间线。如需 Bird 在每个事件发生时将其推送到您的端点,请参阅消息状态 Webhook。回应(Reaction)有独立的历史记录,详见回应事件。
生命周期事件
事件按时间顺序排列。出站消息可能终止于 whatsapp.failed 或 whatsapp.rejected,其 whatsapp.read 事件仅在收件人打开消息时才出现。入站时间线从 whatsapp.received 开始,并可在您的工作区将消息标记为已读后记录 whatsapp.read。
| 事件 | 含义 |
|---|---|
| whatsapp.accepted | Bird 接受了发送请求。这是 202 报告的结果。 |
| whatsapp.sent | Bird 已将消息移交至 WhatsApp 网络。 |
| whatsapp.delivered | WhatsApp 确认消息已投递至收件人的设备。 |
| whatsapp.read | 收件人打开了消息。 |
| whatsapp.failed | 消息未能投递。error.code 说明了阻止原因。 |
| whatsapp.rejected | Bird 在发送前拒绝了消息,未产生费用。 |
| whatsapp.received | Bird 收到了来自联系人的入站消息。 |
适用的 delivered 或 read 回调可触发 Meta 的费用分成。WhatsApp 事件载荷不包含费用信息。使用 GET /v1/whatsapp/messages/{message_id} 回读消息以查看其费用。请参阅费用与计费。
将入站消息标记为已读会在其时间线中记录 whatsapp.read,但不会触发已读确认 webhook。入站消息保持其 received 状态,并在 WhatsApp 接受确认后记录 read_at。
whatsapp.read 不会更改消息的 status。已投递的消息仍保持 delivered;消息同时在 read_at 中记录已读。
whatsapp.delivered 可能被完全跳过。 当收件人的设备上已打开聊天窗口时,Meta 会报告已读而不报告投递,因此时间线显示为 whatsapp.accepted → whatsapp.sent → whatsapp.read,中间没有 whatsapp.delivered。应将 read 视为投递证明:等待 delivered 后才认为消息送达的消费者,恰好会卡在最快看到消息的收件人上;而仅根据 delivered 计算投递率会导致低估。在这种情况下,消息 status 保持 sent,因为只有投递回执才会推进状态。
仅已读的回调仍可触发适用的 Meta 费用。Bird 在投递和已读路径上使用同一个费用标识;缺少投递事件并不意味着 Meta 部分免费。请参阅费用与计费。
事件类型列表是开放的:新类型可能随时添加,因此请将未识别的值视为未来事件而非错误。
失败事件
whatsapp.failed 和 whatsapp.rejected 是终态。拒绝表示 Bird 在将消息发送至 WhatsApp 之前阻止了它,因此不会产生费用。原因包括被屏蔽或已退订的收件人、钱包余额不足或目标地址未配置价格。失败表示消息未能投递,error.code 说明了决策方。大多数错误码携带的是 WhatsApp 的判定,映射自其报告的代码。internal_error 是例外:它记录缺少可用的发送方凭据或处理重试已耗尽。不确定的传输尝试并不能证明 Meta 从未收到请求。meta_error_code 在可用时包含 WhatsApp 的代码,而 internal_error 类型的失败在设计上不包含代码。
两种事件都包含一个 error 对象,该对象带有稳定的 Bird code、可读的 description、可选的 meta_error_code 以及 occurred_at。该对象仅在这些事件类型的 API 记录和 webhook 载荷中出现。
从 API 读取事件
GET /v1/whatsapp/messages/{message_id}/events 按时间顺序返回时间线。有界列表不分页。读取事件需要具有 whatsapp:read 的 API 密钥:
const { data } = await bird.whatsapp.listEvents("wa_abc123");
for (const event of data) console.log(event.type, event.occurred_at);events = client.whatsapp.list_events("wa_abc123")
for event in events.data:
print(event.type, event.occurred_at)events, err := client.Whatsapp.ListEvents(context.Background(), "wam_01krdgeqcxet5s7t44vh8rt9mg", bird.WhatsappListEventsParams{})
if err != nil {
log.Fatal(err)
}
for _, e := range events.Data {
fmt.Println(e.Id, e.Type)
}$events = $bird->whatsapp->listEvents('wamid_01krdgeqcxet5s7t44vh8rt9mg');
foreach ($events->getData() ?? [] as $event) {
echo $event->getType(), ' ', $event->getId(), "\n";
}bird whatsapp list-events <message-id>curl https://us1.platform.bird.com/v1/whatsapp/messages/wam_.../events \
-H "Authorization: Bearer $BIRD_API_KEY"一条经历了接受、发送、投递和已读的消息会返回四个事件:
代码示例
{
"data": [
{
"id": "ev_01ky7q6a1fejfbvs0myn41hj41",
"occurred_at": "2026-07-23T14:48:34.71Z",
"type": "whatsapp.accepted"
},
{
"id": "ev_01ky7q6a2denvtd6jg1vqwmg13",
"occurred_at": "2026-07-23T14:48:35.671Z",
"type": "whatsapp.sent"
},
{
"id": "ev_01ky7q6a2zff9r2qm74mmg1g6z",
"occurred_at": "2026-07-23T14:48:36.642Z",
"type": "whatsapp.delivered"
},
{
"id": "ev_01ky7q6c21frssf0vj8h50qysw",
"occurred_at": "2026-07-23T14:48:38.65Z",
"type": "whatsapp.read"
}
]
}传入 type 可返回一种精确的公共事件类型,例如 ?type=whatsapp.failed 或 ?type=whatsapp.read。省略该参数则返回完整时间线。
该时间线与您在 WhatsApp 日志页面打开消息时看到的内容相同。

后续步骤
- 消息状态 Webhook:在每个事件发生时接收它
- 回应事件:读取当前回应和回应日志
- 标记消息为已读:确认入站消息并显示输入状态
- WhatsApp 日志:呈现此时间线的每条消息视图
- 发送 WhatsApp 消息:消息生命周期的起点