Carruseles multimedia de WhatsApp
Un carrusel multimedia es un conjunto de dos a diez tarjetas que el destinatario desliza una junto a otra, cada una con su propia imagen o video, su propio texto corto y sus propios botones. Úsalo para mostrar varios elementos a la vez, como un puñado de productos, en lugar de enviar un mensaje por cada elemento.
Enviar un carrusel
Establece interactive.type en carousel, con un body_text a nivel de mensaje y un 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 es obligatorio en cada mensaje de servicio: un número que tu espacio de trabajo posee, no uno gestionado por Bird. La forma completa añade el texto propio de una tarjeta, un segundo botón de respuesta rápida y una cita de un mensaje anterior:
Ejemplo 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 un mensaje anterior en la misma conversación. Consulta en el hub citar un mensaje para correlacionar una respuesta para ver cómo funciona la resolución y qué puede omitir.
Un carrusel no admite encabezado ni pie a nivel de mensaje: el body_text del mensaje es el único texto por encima de las tarjetas. Consulta la sección de botones del hub para ver la forma de botón compartida que reutilizan las tarjetas de este tipo.
Tarjetas
Cada tarjeta lleva su propio encabezado multimedia, su propio texto corto y sus propios botones:
- header es obligatorio en cada tarjeta, y solo puede ser image o video: sin texto ni encabezado de documento, a diferencia de los otros tipos interactivos.
- body_text es opcional. Aparece debajo del multimedia de la tarjeta, con un límite más corto que el cuerpo de un mensaje, y permite como máximo dos saltos de línea.
- buttons es obligatorio: un botón cta_url o hasta tres botones quick_reply, nunca una mezcla en la misma tarjeta.
Las tarjetas se muestran de izquierda a derecha en el orden en que aparecen en el array cards. Una tarjeta no tiene pie ni campo de índice propio; su posición en el array es su posición en el carrusel.
Todas las tarjetas llevan los mismos botones
Cada tarjeta de un carrusel debe llevar los mismos tipos de botón, la misma cantidad y en el mismo orden. Un carrusel en el que la tarjeta 1 tiene un botón cta_url y la tarjeta 2 tiene dos botones quick_reply se rechaza, al igual que un carrusel en el que todas las tarjetas tienen dos botones quick_reply pero en un orden distinto.
La razón es cómo WhatsApp renderiza el mensaje: un carrusel es una vista de tarjeta con un diseño compartido, no un conjunto de tarjetas con diseño independiente. Una tarjeta con una fila de botones diferente rompería ese diseño compartido, por lo que WhatsApp exige que todas coincidan y Bird lo comprueba antes de crear o cobrar el envío. Una discrepancia devuelve E15059.
Las etiquetas de los botones son una regla aparte, y su alcance es diferente: una etiqueta debe ser única dentro de una tarjeta, no en todo el carrusel. "Buy now" en cada una de las diez tarjetas es válido; "Buy now" dos veces en la misma tarjeta devuelve E15056.
Límites
| Campo | Restricción |
|---|---|
| cards | 2 a 10 entradas |
| header de tarjeta | obligatorio en cada tarjeta; solo image o video |
| header.url de tarjeta | obligatorio, sin longitud máxima |
| body_text de tarjeta | opcional, de 1 a 160 caracteres, máximo 2 saltos de línea |
| buttons de tarjeta | 1 a 3 entradas: un cta_url, o hasta tres quick_reply, nunca mezclados |
| Etiqueta de botón (quick_reply.text, cta_url.text) | obligatorio, de 1 a 20 caracteres, único dentro de la tarjeta |
| quick_reply.slug | obligatorio, de 1 a 256 caracteres |
| cta_url.url | obligatorio, de 1 a 2000 caracteres |
| body_text del mensaje | obligatorio, de 1 a 1024 caracteres |
| Encabezado y pie del mensaje | no permitidos en un carrusel: sin header, sin footer_text |
Bird limita los botones quick_reply a tres por tarjeta. Meta no indica un límite numérico, solo que una tarjeta acepta un botón de enlace o uno o más botones de respuesta, así que este tope es propio de Bird, no de WhatsApp.
Leer la respuesta
Solo un botón de tarjeta quick_reply produce una respuesta. Al pulsarlo, llega como su propio mensaje entrante con interactive_reply:
Ejemplo 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"
}El slug que estableciste en el botón pulsado vuelve tal cual en interactive_reply.button.slug, la misma forma que produce una pulsación de botones de respuesta. Ves esta respuesta a través de la lista de mensajes o GET /v1/whatsapp/messages/{id}; consulta en el hub leer una respuesta para ver esa ruta completa.
Un botón cta_url en una tarjeta abre su enlace en el navegador del destinatario y no envía nada de vuelta, igual que un botón de enlace independiente.
Carruseles libres y carruseles de plantilla
Esta página cubre el carrusel libre que envías en línea con interactive.type: "carousel", entregable solo dentro de una ventana de servicio al cliente abierta y nunca revisado por Meta. Plantillas de WhatsApp tiene su propio carrusel separado: un componente de plantilla creado una vez, enviado a Meta para aprobación y enviado por slug como cualquier otra plantilla, incluso fuera de la ventana. Los dos comparten la palabra "carousel" y el rango de 2 a 10 tarjetas de Meta, y nada más: formas de transmisión distintas, rutas de revisión distintas, y la cantidad de tarjetas de un carrusel de plantilla se fija en la aprobación de la plantilla en lugar de elegirse por envío. Si estás explorando plantillas y ves "carousel" ahí, ese es el tipo de plantilla, no esta página.
Límites y casos extremos
- La ventana de servicio al cliente debe estar abierta. Un carrusel es un mensaje de servicio, entregable solo dentro de una ventana abierta; consulta en el hub ventana de servicio al cliente. La comprobación de la ventana falla como abierta, así que un 202 no es prueba de que la ventana estuviera realmente abierta cuando se despacha el envío.
- from debe ser un número que tu espacio de trabajo posee. Omitirlo, o indicar un número que no sea un remitente conectado, se rechaza antes de crear el envío.
- El multimedia de la tarjeta debe ser accesible públicamente cuando se despacha el envío. Bird no almacena ni redirige el archivo: WhatsApp obtiene el url de cada tarjeta por sí mismo, en el momento del envío, así que una URL firmada debe durar más que el envío.
- Una URL multimedia de tarjeta que WhatsApp no puede obtener se acepta, luego falla de forma asíncrona, y aun así se cobra. La validación de solicitud de Bird solo comprueba que el url de una tarjeta sea un URI bien formado, no que WhatsApp pueda alcanzarlo ni que use https. Un archivo demasiado grande, un 404, un host irresolvible o un tipo de archivo incorrecto vuelven como 202 en la aceptación, luego whatsapp.accepted, luego whatsapp.sent, luego whatsapp.failed, con media_rejected en el last_error del mensaje y el costo del envío ya cobrado sin vía de reembolso. Prueba la URL de cada tarjeta antes de enviar, ya que una rota no se detecta hasta después del hecho.
- Todas las tarjetas deben llevar los mismos botones. Consulta Todas las tarjetas llevan los mismos botones más arriba; esta es la única regla de carrusel que el esquema de la solicitud no puede expresar por sí solo, por lo que se comprueba por separado y devuelve E15059 en lugar de un error de validación genérico.
- Sin encabezado ni pie a nivel de mensaje. El único texto de un carrusel por encima de las tarjetas es body_text; no hay dónde colocar letra pequeña como lo hacen los otros tipos con footer_text.
- La respuesta no incluye índice de tarjeta. La pulsación de quick_reply en una tarjeta solo reporta {slug, text}, la misma forma que una pulsación de botones de respuesta, sin un campo que indique de qué tarjeta provino. Si necesitas saber qué tarjeta se pulsó, codifica la tarjeta en el slug de cada botón, por ejemplo buy-echeveria en lugar de un buy simple.
- Un botón de tarjeta cta_url no genera ningún evento entrante. Si necesitas saber que se interactuó con una tarjeta, usa botones quick_reply en esa tarjeta, o rastrea el clic en tu propia URL de destino.
Aparte de E15059, el único error interactivo específico de un carrusel es E15056 por una etiqueta de botón repetida en una tarjeta. Una cita que no se resuelve hace fallar la solicitud antes de crear o cobrar nada: 404 E15071 cuando el id no corresponde a ningún mensaje de este espacio de trabajo, 422 E15072 cuando corresponde a uno que no se puede citar. Para los errores que cualquier envío de WhatsApp puede encontrar, como una ventana cerrada, un remitente faltante o inválido, o un destinatario inválido, consulta en el hub errores y Enviar mensajes de WhatsApp.
Próximos pasos
- Mensajes interactivos de WhatsApp: lo que comparten los seis tipos interactivos
- Plantillas de WhatsApp: para un carrusel que se envía fuera de la ventana de servicio al cliente
- Enviar mensajes de WhatsApp: la envoltura de la solicitud, el modelo 202 y reintentos seguros
Recursos relacionados
Continúa con la documentación, guías y ejemplos sobre este tema. Los recursos están en inglés.
Ver la guíaConnecting WhatsApp to Bird: from buying a number to a live channelComprender el conceptoWhat is the 24-hour customer service window on WhatsApp?Usar la herramientaWhatsApp message builderExplorar la funcionalidadWhatsApp
Prueba el ejercicio y obtén un resumen de implementación