Sign inGet started

接收 WhatsApp 消息

入站消息与出站消息使用同一资源,无需轮询单独的收件箱端点。在仪表板中通过消息列表查看,或通过 API 使用 GET /v1/whatsapp/messages/{id} 将列表筛选为入站消息后读取。
每条入站消息都会将客户服务窗口延长至该消息自身时间戳之后的 24 小时,这正是您的自由回复能够送达的前提。延迟到达 Bird 的消息仍携带联系人实际授予的窗口,已有的更晚截止时间不会被缩短。

入站消息包含的内容

每条入站消息共享同一个信封:一个 iddirection: "inbound"from 中的联系人、to 中您自己的号码、值为 receivedstatus,以及一个 created_at。信封旁边恰好有一个内容字段,标识联系人发送的内容:
代码示例
{
  "id": "wam_01kya19eknftrs2s6p82asmvnh",
  "direction": "inbound",
  "from": { "phone_number": "+14155550100" },
  "to": { "phone_number": "+13124495569" },
  "status": "received",
  "text": { "body": "Is my order out for delivery yet?" },
  "created_at": "2026-08-25T09:04:11Z"
}
from 根据 WhatsApp 报告的身份标识联系人:一个 E.164 格式的 phone_number、一个 bsuid,或两者兼有,加上他们公开的 usernamedisplay_name。已设置 WhatsApp 用户名的联系人可以在没有电话号码的情况下联系您;参见业务范围用户 ID了解应存储什么以及如何请求号码。
这些内容字段中的一个,即一个 arm,承载联系人发送的内容,每条消息上恰好设置一个 arm。每个 arm 都有独立的页面,包含读取结构、whatsapp.received 载荷以及注意事项:
字段入站消息包含的内容
textbody,联系人输入的消息
image一个 idurlmime_type,以及任何 caption
video相同的媒体字段,加上任何 caption
audio相同的媒体字段,语音消息上加 voice;无标题字段
sticker相同的媒体字段,加上 animated
document相同的媒体字段,加上任何 filenamecaption
locationlatitudelongitude,有时还有 nameaddress 或一个 url
contact_cards联系人分享的一张或多张联系人卡片
interactive_reply联系人点击的按钮或行的 slugtext
unsupportedAPI 未建模的 WhatsApp 内容类型,例如订单
有两种点击到达的 arm 可能出乎意料。位置请求作为普通的入站 location 返回,联系人信息请求作为 contact_cards 返回,因此只监听 interactive_reply 来捕获点击的集成会遗漏这两者。

获取入站媒体

入站的 imagevideoaudiostickerdocument 到达时是对 Bird 存储的文件的引用,而非文件本身:
代码示例
{
  "image": {
    "id": "waf_01kyb2m4xq7whs0d8n3prv6tez",
    "url": "https://platform.bird.com/v1/whatsapp/messages/wam_01kya19eknftrs2s6p82asmvnh/media/waf_01kyb2m4xq7whs0d8n3prv6tez",
    "mime_type": "image/jpeg",
    "caption": "Is this the right part?"
  }
}
使用通道的媒体方法获取字节数据,传入消息 id 和媒体 id
const media = await bird.whatsapp.messages.media(
  "wam_01kya19eknftrs2s6p82asmvnh",
  "waf_01kyb2m4xq7whs0d8n3prv6tez",
);
console.log(media.contentType, media.contentLength);
返回的字节数据带有为其声明的 mime_type 存储类型。Bird 无法识别的 id 返回 404,出站消息没有可提供的已存储媒体。
消息在到达后 30 天内可回读,其媒体的存续时间不会超过消息本身。 此端点在提供文件之前先读取消息,因此窗口过期后两者都返回 404。如果需要更长时间保留文件,请在消息仍可读时存储。提前结束的唯一情况是存储的字节在窗口到期前被删除:获取时返回 410 E15021,而消息本身仍可回读,带有媒体的 mime_typecaption
底层实现中,此端点以 302 返回一个有效期为 15 分钟的预签名 URL。SDK 和 CLI 会自动处理该跳转。直接调用时,预签名 URL 自带凭证,因此重定向后的请求不得再发送您的 Authorization 请求头。同时发送两者会失败。curl -L 在跨主机重定向时会自动丢弃该请求头;逐字转发请求头的客户端需要将 Location 作为单独的未认证请求获取。

引用回复

当 WhatsApp 将消息标记为回复时,in_reply_to_message_id 指明入站消息所回复的那条消息:
代码示例
{
  "id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
  "direction": "inbound",
  "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
  "text": { "body": "Yes, that one" }
}
WhatsApp 不会标记每条回复,未标记的回复不携带任何 ID。当被引用的消息无法匹配到 Bird 持有的消息时,该字段也会被省略:例如在此工作区开始记录之前发送的消息,或超过 Bird 保留 WhatsApp 自有消息 ID 的 15 天期限的消息。未匹配时省略该字段而非报告一个,这与回复未指向任何消息的情况读取结果相同。
将该字段视为提示而非键。您自己发送时的 metadata 在此无用,因为它保留在您的消息上,不会传递到联系人的回复中,因此需要知道答案属于哪个问题的集成应自行跟踪最后向该联系人提出的问题。参见引用消息了解出站侧的用法。

Webhook

订阅 whatsapp.received 以在入站消息到达时立即处理,而非轮询列表。载荷在事件信封之上携带内容,因此端点无需后续读取:
代码示例
{
  "type": "whatsapp.received",
  "timestamp": "2026-08-25T09:04:11.118Z",
  "data": {
    "whatsapp_id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
    "workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
    "direction": "inbound",
    "from": { "phone_number": "+14155550100", "display_name": "Alex Rivera" },
    "to": { "phone_number": "+13124495569" },
    "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
    "interactive_reply": {
      "type": "button",
      "button": { "slug": "cancel-booking", "text": "Cancel" }
    },
    "tags": null,
    "metadata": null
  }
}
参见 WhatsApp 事件了解完整的信封和其余事件列表。

后续步骤