Menús de lista de WhatsApp
Un menú de lista aparece como un botón tocable debajo de un mensaje de WhatsApp y se expande en filas agrupadas en secciones, para que el destinatario elija una opción de un conjunto más largo en lugar de escribir texto libre. Úsalo cuando la lista de opciones tenga más de tres; para tres o menos, los botones de respuesta son la forma más simple.
- Track my orderFind your latest delivery update
- Collect my orderCheck your collection details
- Talk to our teamGet help from customer care
Enviar un menú de lista
Establece interactive.type en list, con un body_text y un objeto list que contenga button_text y al menos una sección:
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 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 un encabezado de texto opcional, un pie de página, una cita de un mensaje anterior, una segunda sección y una descripción de fila:
{
"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 cita un mensaje anterior en la misma conversación. Consulta en el hub citar un mensaje para correlacionar una respuesta para saber cómo funciona la resolución y qué puede omitir.
Un menú de lista usa list.sections en lugar del objeto de botón compartido; consulta la sección de botones del hub para saber cómo los otros tipos usan esa forma en su lugar.
Secciones y filas
list.button_text es la etiqueta del botón que abre el menú, de hasta 20 caracteres. Al tocarlo se abre el menú: cada sección se muestra bajo su propio title, y cada fila dentro de una sección muestra su etiqueta text con su description opcional debajo, en tipo más pequeño. El title de una sección es obligatorio incluso cuando el menú tiene una sola sección, porque WhatsApp lo muestra encima de las filas de esa sección de todas formas.
El slug de una fila es tu propio identificador para esa fila, nunca se muestra al destinatario. Solo text se muestra en el chat, y el slug se devuelve textualmente en la respuesta, el mismo viaje de ida y vuelta que el hub describe para los botones. Nada valida los caracteres en slug: pon lo que tu propia lógica de enrutamiento necesite ahí.
Límites
| Campo | Límite |
|---|---|
| Secciones por mensaje | 1 a 10 |
| Filas por sección | 1 a 10 |
| Filas en todas las secciones | 10, en total |
title de sección | obligatorio, 1 a 24 caracteres |
slug de fila | obligatorio, 1 a 200 caracteres, cualquier carácter |
text de fila (etiqueta) | obligatorio, 1 a 24 caracteres, único en todo el mensaje |
description de fila | opcional, hasta 72 caracteres |
list.button_text | obligatorio, 1 a 20 caracteres |
body_text | obligatorio, 1 a 4096 caracteres |
footer_text | opcional, hasta 60 caracteres |
header.text | 1 a 60 caracteres |
Dos de estos límites son fáciles de malinterpretar.
El tope de filas es diez en todas las secciones, no diez por sección. Cada sección permite hasta 10 filas de forma independiente, pero el mensaje en su totalidad sigue teniendo un máximo de 10 filas en total. Tres secciones de 4 filas cada una pasan todas las comprobaciones por sección y aun así superan el total, que se aplica por separado del conteo por sección y devuelve E15055 con un mensaje que indica cuántas filas ofreció realmente el envío.
body_text es el único que conserva los 4096 caracteres completos. Todos los demás tipos interactivos reducen su cuerpo a 1024; un menú de lista es el único que WhatsApp permite usar con la longitud completa, así que recurre a él cuando la elección necesite más contexto que un mensaje corto.
Leer la elección
Un toque en una fila llega como su propio mensaje entrante, con 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"
}El slug que estableciste en la fila elegida se devuelve textualmente, así que puedes bifurcar directamente con él. description solo está presente cuando la fila lo incluía. 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 conocer esa ruta completa.
Límites y casos extremos
- La ventana de atención al cliente debe estar abierta. Un menú de lista es un mensaje de servicio, entregable solo dentro de una ventana abierta; consulta la ventana de atención al cliente del hub. La comprobación de ventana falla como abierta, así que un
202no es prueba de que la ventana estaba realmente abierta cuando se realiza el envío. fromdebe 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 que se cree el envío.- El encabezado es solo texto, y un encabezado multimedia falla como error genérico. Un menú de lista es el único tipo interactivo cuyo encabezado no puede llevar imagen, video ni documento. Enviar uno de todas formas no devuelve un código de error específico de WhatsApp: falla en la validación general de la solicitud, así que un desarrollador depurando por código de error solo ve que la solicitud falló la validación y tiene que leer los detalles para encontrar el encabezado.
- El tope de filas entre secciones es la trampa. Los límites por sección y el total del mensaje tienen ambos un tope de 10, así que parecen el mismo número repetido. No son acumulativos: un menú que pasa todas las comprobaciones por sección aún puede superar el total, y solo el total es lo que se aplica.
- Las etiquetas de fila deben ser únicas en todo el mensaje, no solo dentro de una sección. Dos filas que comparten una etiqueta
text, incluso en secciones diferentes, se rechazan con E15056. Esta es una regla propia de Bird, no una que Meta declare: la respuesta nombra la etiqueta tocada, así que un duplicado dejaría la respuesta ambigua, y valores distintos deslugdebajo no ayudan a resolverlo. - Un rechazo del lado de Meta más allá de las comprobaciones propias de Bird es vago y puede llegar tarde. Las comprobaciones de conteo de filas y etiquetas duplicadas de Bird capturan los dos errores más comunes antes de que se cree nada. Lo que Meta rechace más allá de eso aparece después del
202, en ellast_errordel mensaje, no en la llamada de envío.
Una cita que no se resuelve falla la solicitud antes de que se cree o cobre nada: 404 E15071 cuando el id no nombra ningún mensaje que este espacio de trabajo tenga, 422 E15072 cuando nombra uno que no puede citarse. Para los errores que cualquier envío de WhatsApp puede producir, una ventana cerrada, un remitente faltante o inválido, o un destinatario inválido, consulta los errores del hub y Enviar mensajes de WhatsApp.
Próximos pasos
- Mensajes interactivos de WhatsApp: lo que comparten los seis tipos interactivos
- Botones de respuesta: para tres opciones o menos
- Enviar mensajes de WhatsApp: la estructura de la solicitud, el modelo de
202y reintentos seguros
Recursos relacionados
Continúa con la documentación, guías y ejemplos de este tema.