Carrosséis de mídia WhatsApp
Um carrossel de mídia é um conjunto de dois a dez cards que o destinatário desliza lado a lado, cada um com sua própria imagem ou vídeo, seu próprio texto curto e seus próprios botões. Use-o para mostrar vários itens de uma vez, como um punhado de produtos, em vez de enviar uma mensagem por item.
Enviar um carrossel
Defina interactive.type como carousel, com um body_text no nível da mensagem e um array cards de 2 a 10 entradas:
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 é obrigatório em toda mensagem de serviço: um número que seu espaço de trabalho possui, não um gerenciado por Bird. A forma completa adiciona o texto próprio do card, um segundo botão de resposta rápida e a citação de uma mensagem anterior:
Exemplo de código
{
"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 cita uma mensagem anterior na mesma conversa. Consulte a seção citar uma mensagem para correlacionar uma resposta do hub para saber como a resolução funciona e o que ela pode perder.
Um carrossel não aceita header nem footer no nível da mensagem: o body_text da mensagem é o único texto acima dos cards. Consulte a seção botões do hub para ver a forma compartilhada de botão que os cards desse tipo reutilizam.
Cards
Cada card carrega seu próprio header de mídia, seu próprio texto curto e seus próprios botões:
- header é obrigatório em todo card, e aceita apenas image ou video: sem texto e sem header de documento, diferente dos outros tipos interativos.
- body_text é opcional. Fica abaixo da mídia do card, com limite menor que o corpo de uma mensagem, e permite no máximo duas quebras de linha.
- buttons é obrigatório: um botão cta_url ou até três botões quick_reply, nunca uma combinação no mesmo card.
Os cards são renderizados da esquerda para a direita na ordem em que aparecem no array cards. Um card não tem footer nem campo de índice próprio; sua posição no array é sua posição no carrossel.
Todo card carrega os mesmos botões
Todo card em um carrossel deve carregar os mesmos tipos de botão, o mesmo número deles e na mesma ordem. Um carrossel em que o card 1 tem um botão cta_url e o card 2 tem dois botões quick_reply é recusado, assim como um carrossel em que todo card tem dois botões quick_reply mas em ordem diferente.
O motivo é como WhatsApp renderiza a mensagem: um carrossel é uma visualização de card com layout compartilhado, não um conjunto de cards com layouts independentes. Um card com uma linha de botões diferente quebraria esse layout compartilhado, então WhatsApp exige que todos os cards sejam iguais e Bird verifica isso antes que o envio seja criado ou cobrado. Uma incompatibilidade retorna E15059.
Os rótulos dos botões são uma regra separada, com escopo diferente: um rótulo deve ser único dentro de um card, não em todo o carrossel. "Buy now" em cada um dos dez cards é válido; "Buy now" duas vezes no mesmo card retorna E15056.
Limites
| Campo | Limite |
|---|---|
| cards | 2 a 10 entradas |
| Card header | obrigatório em todo card; apenas image ou video |
| Card header.url | obrigatório, sem limite máximo de tamanho |
| Card body_text | opcional, 1 a 160 caracteres, no máximo 2 quebras de linha |
| Card buttons | 1 a 3 entradas: um cta_url, ou até três quick_reply, nunca misturados |
| Rótulo do botão (quick_reply.text, cta_url.text) | obrigatório, 1 a 20 caracteres, único dentro do card |
| quick_reply.slug | obrigatório, 1 a 256 caracteres |
| cta_url.url | obrigatório, 1 a 2.000 caracteres |
| Message body_text | obrigatório, 1 a 1.024 caracteres |
| Header e footer da mensagem | não permitidos em um carrossel: sem header, sem footer_text |
Bird limita botões quick_reply a três por card. A própria Meta não define um limite numérico, apenas que um card aceita um botão de link ou um ou mais botões de resposta, então esse teto é de Bird, não de WhatsApp.
Lendo a resposta
Apenas um botão quick_reply em um card produz uma resposta. Um toque nele chega como sua própria mensagem de entrada, carregando interactive_reply:
Exemplo de código
{
"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"
}O slug que você definiu no botão tocado retorna tal qual em interactive_reply.button.slug, a mesma forma que um toque em botões de resposta produz. Você vê essa resposta pela lista de mensagens ou por GET /v1/whatsapp/messages/{id}; consulte a seção lendo uma resposta do hub para o caminho completo.
Um botão cta_url em um card abre o link no navegador do destinatário e não envia nada de volta, da mesma forma que um botão de link avulso.
Carrosséis livres e carrosséis de template
Esta página cobre o carrossel livre que você envia inline com interactive.type: "carousel", entregável apenas dentro de uma janela de atendimento ao cliente aberta e nunca revisado pela Meta. Templates WhatsApp tem seu próprio carrossel separado: um componente de template criado uma vez, enviado à Meta para aprovação e enviado por slug como qualquer outro template, inclusive fora da janela. Os dois compartilham a palavra "carousel" e a faixa de 2 a 10 cards da Meta, e nada mais: formatos de transmissão diferentes, caminhos de revisão diferentes, e a quantidade de cards de um carrossel de template é fixada na aprovação do template, não escolhida a cada envio. Se você estiver navegando por templates e vir "carousel" lá, esse é o tipo de template, não esta página.
Limites e casos extremos
- A janela de atendimento ao cliente precisa estar aberta. Um carrossel é uma mensagem de serviço, entregável apenas dentro de uma janela aberta; consulte a seção janela de atendimento ao cliente do hub. A verificação da janela falha de forma aberta, então um 202 não é prova de que a janela estava de fato aberta quando o envio é feito.
- from deve ser um número que seu espaço de trabalho possui. Omiti-lo, ou informar um número que não é um remetente conectado, é rejeitado antes que o envio seja criado.
- A mídia do card precisa estar acessível publicamente quando o envio é despachado. Bird não armazena nem faz proxy do arquivo: WhatsApp busca o url de cada card no momento do envio, então uma URL assinada precisa durar mais que o envio.
- Uma URL de mídia do card que WhatsApp não consegue buscar é aceita, depois falha de forma assíncrona, e ainda assim é cobrada. A validação de solicitação de Bird verifica apenas se o url de um card é um URI bem formado, não se WhatsApp consegue acessá-lo ou se ele usa https. Um arquivo grande demais, um 404, um host irresolvível ou o tipo de arquivo errado retornam como 202 no aceite, depois whatsapp.accepted depois whatsapp.sent depois whatsapp.failed, com media_rejected no last_error da mensagem e o custo do envio já cobrado sem caminho de reembolso. Teste a URL de cada card antes de enviar, pois uma URL quebrada só é detectada depois do fato.
- Todo card deve carregar os mesmos botões. Veja Todo card carrega os mesmos botões acima; esta é a única regra de carrossel que o schema da solicitação não consegue expressar sozinho, então é verificada separadamente e retorna E15059 em vez de um erro genérico de validação.
- Sem header ou footer no nível da mensagem. O único texto de um carrossel acima dos cards é body_text; não há onde colocar texto pequeno da forma que os outros tipos usam footer_text.
- A resposta não traz índice do card. Um toque em quick_reply de um card reporta apenas {slug, text}, a mesma forma de um toque em botões de resposta, sem campo indicando de qual card veio. Se você precisa saber qual card foi tocado, codifique o card no slug de cada botão, como buy-echeveria em vez de um simples buy.
- Um botão cta_url em um card não gera evento de entrada. Se você precisa saber que houve interação com um card, use botões quick_reply nesse card, ou rastreie o clique na sua própria URL de destino.
Além de E15059, o único erro interativo específico de um carrossel é E15056 para um rótulo de botão repetido em um card. Uma citação que não resolve falha a solicitação antes que qualquer coisa seja criada ou cobrada: 404 E15071 quando o id não corresponde a nenhuma mensagem que esse espaço de trabalho possui, 422 E15072 quando corresponde a uma que não pode ser citada. Para os erros que qualquer envio WhatsApp pode encontrar, uma janela fechada, um remetente ausente ou inválido, ou um destinatário inválido, consulte as seções erros e Enviando mensagens WhatsApp do hub.
Próximos passos
- Mensagens interativas WhatsApp: o que todos os seis tipos interativos compartilham
- Templates WhatsApp: para um carrossel enviado fora da janela de atendimento ao cliente
- Enviando mensagens WhatsApp: o envelope da solicitação, o modelo 202 e novas tentativas seguras
Recursos relacionados
Continue com a documentação, guias e exemplos sobre este tópico. Os recursos estão em inglês.
Assista ao guiaConnecting WhatsApp to Bird: from buying a number to a live channelEntenda o conceitoWhat is the 24-hour customer service window on WhatsApp?Use a ferramentaWhatsApp message builderExplore a funcionalidadeWhatsApp
Experimente na prática e obtenha um resumo de implementação