WhatsApp 媒体轮播
媒体轮播是一组 2 到 10 张卡片,收件人可以左右滑动浏览,每张卡片有各自的图片或视频、短文本和按钮。当你需要同时展示多个条目(例如几件商品)时,使用轮播而不是逐条发送消息。
发送轮播
将 interactive.type 设置为 carousel,并指定一个消息级别的 body_text 和一个包含 2 到 10 个条目的 cards 数组:
const msg = await bird.whatsapp.send({
to: "+16505551234",
from: "+13124495648",
interactive: {
type: "carousel",
body_text: "Here are two of our latest arrivals, each under $25:",
cards: [
{
header: { type: "image", url: "https://cdn.example.com/plants/blue-echeveria.jpeg" },
buttons: [
{
type: "cta_url",
cta_url: { text: "Buy now", url: "https://shop.example.com/blue-echeveria" },
},
],
},
{
header: { type: "image", url: "https://cdn.example.com/plants/zebra-haworthia.jpeg" },
buttons: [
{
type: "cta_url",
cta_url: { text: "Buy now", url: "https://shop.example.com/zebra-haworthia" },
},
],
},
],
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+16505551234",
from_="+13124495648",
interactive={
"type": "carousel",
"body_text": "Here are two of our latest arrivals, each under $25:",
"cards": [
{
"header": {"type": "image", "url": "https://cdn.example.com/plants/blue-echeveria.jpeg"},
"buttons": [{"type": "cta_url", "cta_url": {"text": "Buy now", "url": "https://shop.example.com/blue-echeveria"}}],
},
{
"header": {"type": "image", "url": "https://cdn.example.com/plants/zebra-haworthia.jpeg"},
"buttons": [{"type": "cta_url", "cta_url": {"text": "Buy now", "url": "https://shop.example.com/zebra-haworthia"}}],
},
],
},
)
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: "carousel",
BodyText: "Here are two of our latest arrivals, each under $25:",
Cards: &[]bird.WhatsAppInteractiveCardSend{
{
Header: bird.WhatsAppInteractiveCardHeaderSend{Type: "image", Url: "https://cdn.example.com/plants/blue-echeveria.jpeg"},
Buttons: []bird.WhatsAppInteractiveButtonSend{{Type: "cta_url", CtaUrl: &bird.WhatsAppInteractiveCtaUrlSend{Text: "Buy now", Url: "https://shop.example.com/blue-echeveria"}}},
},
{
Header: bird.WhatsAppInteractiveCardHeaderSend{Type: "image", Url: "https://cdn.example.com/plants/zebra-haworthia.jpeg"},
Buttons: []bird.WhatsAppInteractiveButtonSend{{Type: "cta_url", CtaUrl: &bird.WhatsAppInteractiveCtaUrlSend{Text: "Buy now", Url: "https://shop.example.com/zebra-haworthia"}}},
},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$interactive = (new WhatsAppMessageSendRequestInteractive())
->setType('carousel')
->setBodyText('Here are two of our latest arrivals, each under $25:')
->setCards([
(new WhatsAppInteractiveCardSend())
->setHeader((new WhatsAppInteractiveCardSendHeader())->setType('image')->setUrl('https://cdn.example.com/plants/blue-echeveria.jpeg'))
->setButtons([
(new WhatsAppInteractiveButtonSend())
->setType('cta_url')
->setCtaUrl((new WhatsAppInteractiveButtonSendCtaUrl())->setText('Buy now')->setUrl('https://shop.example.com/blue-echeveria')),
]),
(new WhatsAppInteractiveCardSend())
->setHeader((new WhatsAppInteractiveCardSendHeader())->setType('image')->setUrl('https://cdn.example.com/plants/zebra-haworthia.jpeg'))
->setButtons([
(new WhatsAppInteractiveButtonSend())
->setType('cta_url')
->setCtaUrl((new WhatsAppInteractiveButtonSendCtaUrl())->setText('Buy now')->setUrl('https://shop.example.com/zebra-haworthia')),
]),
]);
$message = $bird->whatsapp->send(
to: '+16505551234',
from: '+13124495648',
interactive: $interactive,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--from +13124495648 \
--interactive '{"body_text":"Here are two of our latest arrivals, each under $25:","cards":[{"buttons":[{"cta_url":{"text":"Buy now","url":"https://shop.example.com/blue-echeveria"},"type":"cta_url"}],"header":{"type":"image","url":"https://cdn.example.com/plants/blue-echeveria.jpeg"}},{"buttons":[{"cta_url":{"text":"Buy now","url":"https://shop.example.com/zebra-haworthia"},"type":"cta_url"}],"header":{"type":"image","url":"https://cdn.example.com/plants/zebra-haworthia.jpeg"}}],"type":"carousel"}' \
--to +16505551234{
"name": "whatsapp_send",
"arguments": {
"from": "+13124495648",
"interactive": {
"body_text": "Here are two of our latest arrivals, each under $25:",
"cards": [
{
"buttons": [
{
"cta_url": {
"text": "Buy now",
"url": "https://shop.example.com/blue-echeveria"
},
"type": "cta_url"
}
],
"header": {
"type": "image",
"url": "https://cdn.example.com/plants/blue-echeveria.jpeg"
}
},
{
"buttons": [
{
"cta_url": {
"text": "Buy now",
"url": "https://shop.example.com/zebra-haworthia"
},
"type": "cta_url"
}
],
"header": {
"type": "image",
"url": "https://cdn.example.com/plants/zebra-haworthia.jpeg"
}
}
],
"type": "carousel"
},
"to": "+16505551234"
}
}curl -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": "carousel",
"body_text": "Here are two of our latest arrivals, each under $25:",
"cards": [
{
"header": { "type": "image", "url": "https://cdn.example.com/plants/blue-echeveria.jpeg" },
"buttons": [{ "type": "cta_url", "cta_url": { "text": "Buy now", "url": "https://shop.example.com/blue-echeveria" } }]
},
{
"header": { "type": "image", "url": "https://cdn.example.com/plants/zebra-haworthia.jpeg" },
"buttons": [{ "type": "cta_url", "cta_url": { "text": "Buy now", "url": "https://shop.example.com/zebra-haworthia" } }]
}
]
}
}'from 在每条服务消息中都是必填项:必须是你的工作区拥有的号码,而不是 Bird 托管的号码。完整结构还可以添加卡片自身的文本、第二个快速回复按钮,以及对早期消息的引用:
代码示例
{
"to": "+16505551234",
"from": "+13124495648",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"interactive": {
"type": "carousel",
"body_text": "Here are two of our latest arrivals, each under $25:",
"cards": [
{
"header": { "type": "image", "url": "https://cdn.example.com/plants/blue-echeveria.jpeg" },
"body_text": "Blue Echeveria. Powdery blue leaves.",
"buttons": [
{ "type": "quick_reply", "quick_reply": { "slug": "buy-echeveria", "text": "Buy" } },
{ "type": "quick_reply", "quick_reply": { "slug": "info-echeveria", "text": "Details" } }
]
},
{
"header": { "type": "image", "url": "https://cdn.example.com/plants/zebra-haworthia.jpeg" },
"body_text": "Zebra Haworthia. White stripes on deep green leaves.",
"buttons": [
{ "type": "quick_reply", "quick_reply": { "slug": "buy-haworthia", "text": "Buy" } },
{ "type": "quick_reply", "quick_reply": { "slug": "info-haworthia", "text": "Details" } }
]
}
]
},
"tags": [{ "name": "category", "value": "catalog" }],
"metadata": { "order_id": "A-1" }
}in_reply_to_message_id 引用同一会话中的早期消息。请参阅 Hub 的引用消息以关联回复,了解解析机制及其可能遗漏的情况。
轮播不接受消息级别的请求头和页脚:消息的 body_text 是卡片上方唯一的文本。请参阅 Hub 的按钮部分,了解该类型的卡片复用的共享按钮结构。
卡片
每张卡片有各自的媒体请求头、短文本和按钮:
- header 在每张卡片上都是必填项,且只能为 image 或 video:不支持文本和文档请求头,与其他交互类型不同。
- body_text 是可选的。它位于卡片媒体下方,长度上限比消息正文短,最多允许两个换行符。
- buttons 是必填项:一个 cta_url 按钮,或最多三个 quick_reply 按钮,同一张卡片上不可混用。
卡片按照在 cards 数组中出现的顺序从左到右渲染。卡片没有页脚,也没有自身的索引字段;它在数组中的位置就是它在轮播中的位置。
每张卡片携带相同的按钮
轮播中的每张卡片必须携带相同的按钮类型、相同的数量、相同的顺序。如果卡片 1 有一个 cta_url 按钮而卡片 2 有两个 quick_reply 按钮,轮播会被拒绝;如果每张卡片都有两个 quick_reply 按钮但顺序不同,同样会被拒绝。
原因在于 WhatsApp 渲染消息的方式:轮播是一个具有共享布局的单卡片视图,而非一组独立布局的卡片。不同的按钮行会破坏共享布局,因此 WhatsApp 要求每张卡片保持一致,Bird 会在创建或计费发送之前进行检查。不匹配时返回 E15059。
按钮标签有单独的规则,其作用范围不同:标签必须在单张卡片内唯一,而非在整个轮播中唯一。十张卡片上都使用 "Buy now" 没有问题;同一张卡片上出现两次 "Buy now" 则返回 E15056。
限制
| 字段 | 限制 |
|---|---|
| cards | 2 到 10 个条目 |
| 卡片 header | 每张卡片必填;仅限 image 或 video |
| 卡片 header.url | 必填,无最大长度限制 |
| 卡片 body_text | 可选,1 到 160 个字符,最多 2 个换行符 |
| 卡片 buttons | 1 到 3 个条目:一个 cta_url,或最多三个 quick_reply,不可混用 |
| 按钮标签(quick_reply.text、cta_url.text) | 必填,1 到 20 个字符,在卡片内唯一 |
| quick_reply.slug | 必填,1 到 256 个字符 |
| cta_url.url | 必填,1 到 2000 个字符 |
| 消息 body_text | 必填,1 到 1024 个字符 |
| 消息请求头、页脚 | 轮播中不允许:无 header,无 footer_text |
Bird 将每张卡片的 quick_reply 按钮上限设为三个。Meta 本身未声明数量限制,仅说明卡片接受一个链接按钮或一个或多个回复按钮,因此这一上限是 Bird 自身的,而非 WhatsApp 的。
读取回复
只有 quick_reply 卡片按钮会产生回复。点击后,回复作为独立的入站消息到达,携带 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": "button",
"button": {
"slug": "buy-echeveria",
"text": "Buy"
}
},
"created_at": "2026-08-25T09:04:11Z"
}你在被点击按钮上设置的 slug 会原样返回到 interactive_reply.button.slug 上,与回复按钮点击产生的结构相同。你可以通过消息列表或 GET /v1/whatsapp/messages/{id} 查看此回复;请参阅 Hub 的读取回复了解完整路径。
卡片上的 cta_url 按钮会在收件人的浏览器中打开链接,不会发送任何回传,与独立的链接按钮相同。
自由格式轮播与模板轮播
本页介绍的是通过 interactive.type: "carousel" 内联发送的自由格式轮播,它只能在客户服务窗口打开时投递,且无需经过 Meta 审核。WhatsApp 模板有自己独立的轮播:模板组件只需创建一次,提交给 Meta 审批,然后像其他模板一样通过 slug 发送,包括在窗口之外。两者共享 "carousel" 一词和 Meta 的 2 到 10 张卡片范围,除此之外没有任何相同之处:不同的传输格式、不同的审核路径,且模板轮播的卡片数量在模板审批时固定,而非每次发送时选择。如果你在浏览模板时看到 "carousel",那是模板类型,不是本页内容。
限制与边界情况
- 客户服务窗口必须处于打开状态。 轮播是一条服务消息,只能在窗口打开时投递;请参阅 Hub 的客户服务窗口。窗口检查采用开放式失败策略,因此 202 并不能证明发送时窗口确实是打开的。
- from 必须是你的工作区拥有的号码。 省略该字段或指定一个非已连接发送方的号码,会在创建发送之前被拒绝。
- 卡片媒体在发送调度时必须可公开访问。 Bird 不存储也不代理文件:WhatsApp 在发送时自行获取每张卡片的 url,因此签名 URL 的有效期必须长于发送耗时。
- WhatsApp 无法获取的卡片媒体 URL 会被接受,然后异步失败,且仍会被计费。 Bird 的请求验证仅检查卡片的 url 是否为格式正确的 URI,而不检查 WhatsApp 能否访问它或它是否使用 https。超大文件、404、无法解析的主机或错误的文件类型都会在接受时返回 202,然后 whatsapp.accepted,然后 whatsapp.sent,然后 whatsapp.failed,消息的 last_error 上会显示 media_rejected,且发送费用已扣除且无退款途径。发送前请测试每张卡片的 URL,因为损坏的 URL 只有在事后才会被发现。
- 每张卡片必须携带相同的按钮。 请参阅上方的每张卡片携带相同的按钮;这是请求 schema 无法独立表达的唯一轮播规则,因此会单独检查并返回 E15059,而非通用验证错误。
- 无消息级别的请求头或页脚。 轮播中卡片上方的唯一文本是 body_text;不像其他类型那样有地方使用 footer_text 放置附加说明。
- 回复不携带卡片索引。 卡片的 quick_reply 点击仅报告 {slug, text},与回复按钮点击的结构相同,没有字段标明来自哪张卡片。如果你需要知道被点击的是哪张卡片,请在每个按钮的 slug 中编码卡片信息,例如使用 buy-echeveria 而非单独的 buy。
- cta_url 卡片按钮不会生成入站事件。 如果你需要了解卡片是否被互动过,请在该卡片上使用 quick_reply 按钮,或在你自己的目标 URL 上追踪点击。
除 E15059 外,轮播特有的唯一交互错误是 E15056,表示同一张卡片上出现了重复的按钮标签。无法解析的引用会在创建或计费之前使请求失败:当 id 指定了该工作区中不存在的消息时返回 404 E15071,当 id 指定了无法被引用的消息时返回 422 E15072。关于任何 WhatsApp 发送都可能遇到的错误(窗口已关闭、发送方缺失或无效、收件人无效),请参阅 Hub 的错误和发送 WhatsApp 消息。
后续步骤
- WhatsApp 交互消息:六种交互类型的共同内容
- WhatsApp 模板:用于在客户服务窗口之外发送轮播
- 发送 WhatsApp 消息:请求信封、202 模型以及安全重试