WhatsApp 列表菜单
列表菜单以可点击按钮的形式显示在 WhatsApp 消息下方,展开后呈现按分区分组的行,收件人从较长的选项列表中选择一个,无需输入自由文本。当选项超过三个时使用列表菜单;三个或更少时,回复按钮是更简单的形式。
- Track my orderFind your latest delivery update
- Collect my orderCheck your collection details
- Talk to our teamGet help from customer care
发送列表菜单
将 interactive.type 设置为 list,附带一个 body_text 和一个包含 button_text 及至少一个分区的 list 对象:
const msg = await bird.whatsapp.send({
to: "+16505551234",
from: "+13124495648",
interactive: {
type: "list",
body_text: "Which shipping option do you prefer?",
list: {
button_text: "Shipping options",
sections: [
{
title: "As soon as possible",
rows: [{ slug: "priority_express", text: "Priority Mail Express" }],
},
],
},
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+16505551234",
from_="+13124495648",
interactive={
"type": "list",
"body_text": "Which shipping option do you prefer?",
"list": {
"button_text": "Shipping options",
"sections": [
{
"title": "As soon as possible",
"rows": [{"slug": "priority_express", "text": "Priority Mail Express"}],
}
],
},
},
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+16505551234",
From: "+13124495648",
Interactive: &bird.WhatsAppInteractiveSend{
Type: "list",
BodyText: "Which shipping option do you prefer?",
List: &bird.WhatsAppInteractiveListSend{
ButtonText: "Shipping options",
Sections: []bird.WhatsAppInteractiveListSectionSend{
{
Title: "As soon as possible",
Rows: []bird.WhatsAppInteractiveListRowSend{
{Slug: "priority_express", Text: "Priority Mail Express"},
},
},
},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$interactive = (new WhatsAppMessageSendRequestInteractive())
->setType('list')
->setBodyText('Which shipping option do you prefer?')
->setList(
(new WhatsAppInteractiveSendList())
->setButtonText('Shipping options')
->setSections([
(new WhatsAppInteractiveListSectionSend())
->setTitle('As soon as possible')
->setRows([
(new WhatsAppInteractiveListRowSend())
->setSlug('priority_express')
->setText('Priority Mail Express'),
]),
]),
);
$message = $bird->whatsapp->send(
to: '+16505551234',
from: '+13124495648',
interactive: $interactive,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--from +13124495648 \
--interactive '{"body_text":"Which shipping option do you prefer?","list":{"button_text":"Shipping options","sections":[{"rows":[{"slug":"priority_express","text":"Priority Mail Express"}],"title":"As soon as possible"}]},"type":"list"}' \
--to +16505551234curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+16505551234",
"from": "+13124495648",
"interactive": {
"type": "list",
"body_text": "Which shipping option do you prefer?",
"list": {
"button_text": "Shipping options",
"sections": [
{
"title": "As soon as possible",
"rows": [{ "slug": "priority_express", "text": "Priority Mail Express" }]
}
]
}
}
}'from 在每条服务消息中都是必需的:必须是您的工作区拥有的号码,而非 Bird 托管的号码。完整结构还可添加可选的文本标题、页脚、对早期消息的引用、第二个分区以及行描述:
{
"to": "+16505551234",
"from": "+13124495648",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"interactive": {
"type": "list",
"header": { "type": "text", "text": "Choose a shipping option" },
"body_text": "Which shipping option do you prefer?",
"footer_text": "Lucky Shrub",
"list": {
"button_text": "Shipping options",
"sections": [
{
"title": "As soon as possible",
"rows": [
{
"slug": "priority_express",
"text": "Priority Mail Express",
"description": "Next day to 2 days"
}
]
},
{
"title": "I can wait a bit",
"rows": [
{
"slug": "ground_advantage",
"text": "Ground Advantage",
"description": "2 to 5 days"
}
]
}
]
}
},
"tags": [{ "name": "flow", "value": "shipping-choice" }],
"metadata": { "order_id": "A1B2C3" }
}in_reply_to_message_id 引用同一会话中的早期消息。请参阅中心文档的引用消息以关联回复,了解解析方式及其可能遗漏的情况。
列表菜单使用 list.sections 而非共享的按钮对象;请参阅中心文档的按钮部分,了解其他类型如何使用该结构。
分区和行
list.button_text 是打开菜单的按钮上的标签,最多 20 个字符。点击后菜单展开:每个分区在其 title 下渲染,分区中的每一行显示其 text 标签,可选的 description 以较小字体显示在下方。即使菜单只有一个分区,分区的 title 也是必需的,因为 WhatsApp 会始终在该分区的行上方显示它。
行的 slug 是您自己为该行设定的标识符,不会向收件人展示。只有 text 会在聊天中渲染,slug 会在回复中原样返回,与中心文档描述的按钮往返机制相同。slug 中的字符不会被校验:放入任何您自己路由逻辑需要的内容即可。
限制
| 字段 | 限制 |
|---|---|
| 每条消息的分区数 | 1 到 10 |
| 每个分区的行数 | 1 到 10 |
| 所有分区的总行数 | 总计 10 |
分区 title | 必需,1 到 24 个字符 |
行 slug | 必需,1 到 200 个字符,任意字符 |
行 text(标签) | 必需,1 到 24 个字符,在整条消息中唯一 |
行 description | 可选,最多 72 个字符 |
list.button_text | 必需,1 到 20 个字符 |
body_text | 必需,1 到 4096 个字符 |
footer_text | 可选,最多 60 个字符 |
header.text | 1 到 60 个字符 |
其中两个限制容易被误读。
行数上限是所有分区合计十行,而非每个分区十行。 每个分区独立允许最多 10 行,但整条消息的总行数上限仍为 10。三个分区各 4 行虽能通过每个分区的检查,但总数仍超限。总数限制独立于每分区计数进行校验,超出时返回 E15055,并在消息中指出发送实际提供了多少行。
body_text 是唯一保留完整 4096 个字符的类型。 其他所有交互类型将正文缩减到 1024 个字符;列表菜单是 WhatsApp 唯一允许使用完整长度的类型,因此当选项需要比简短提示更多的上下文时,请选择它。
读取选择结果
行点击以独立的入站消息到达,携带 interactive_reply:
{
"id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
"direction": "inbound",
"from": { "phone_number": "+16505551234" },
"to": { "phone_number": "+13124495648" },
"status": "received",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"interactive_reply": {
"type": "list",
"list": {
"slug": "priority_express",
"text": "Priority Mail Express",
"description": "Next day to 2 days"
}
},
"created_at": "2026-08-25T09:04:11Z"
}您在所选行上设置的 slug 会原样返回,因此可以直接用它进行分支判断。description 仅在该行携带了描述时才存在。您可以通过消息列表或 GET /v1/whatsapp/messages/{id} 查看此回复;请参阅中心文档的读取回复了解完整路径。
限制和边界情况
- 客户服务窗口必须处于打开状态。 列表菜单是服务消息,仅在窗口打开时可投递;请参阅中心文档的客户服务窗口。窗口检查以放行方式失败,因此
202并不能证明发送时窗口确实处于打开状态。 from必须是您的工作区拥有的号码。 省略它或指定一个未连接的发送方号码,请求会在发送创建之前被拒绝。- 标题仅支持文本,媒体标题会以通用错误失败。 列表是唯一不能在标题中携带图片、视频或文档的交互类型。强行发送不会返回 WhatsApp 特定的错误码:它会在通用请求校验中失败,因此按错误码调试的开发者只能看到请求校验失败,必须阅读详情才能找到标题问题。
- 跨分区行数上限是陷阱。 每分区限制和消息级总数都上限为 10,看起来像同一个数字出现了两次。但它们不是累加关系:通过每分区检查的菜单仍可能超出总数限制,而最终被校验的是总数。
- 行标签在整条消息中必须唯一,而非仅在分区内唯一。 两行共享同一个
text标签(即使在不同分区中)会被拒绝并返回 E15056。这是 Bird 自身的规则,并非 Meta 明确声明的:回复中会指明被点击的标签,因此重复标签会使答案产生歧义,即使下方的slug值不同也无法解决。 - 超出 Bird 自身检查范围的 Meta 侧拒绝是模糊的,且可能延迟到达。 Bird 的行数和重复标签检查会在任何内容创建之前捕获两个最常见的错误。Meta 在此之外拒绝的内容会在
202之后出现在消息的last_error上,而非在发送调用本身上。
无法解析的引用会在创建或计费之前使请求失败:当 id 指定的消息不在此工作区中时返回 404 E15071,当指定的消息无法被引用时返回 422 E15072。关于任何 WhatsApp 发送可能遇到的错误(窗口关闭、发送方缺失或无效、收件人无效),请参阅中心文档的错误和发送 WhatsApp 消息。
后续步骤
- WhatsApp 交互式消息:六种交互类型的共同点
- 回复按钮:适用于三个或更少的选项
- 发送 WhatsApp 消息:请求信封、
202模型和安全重试