Mensajes interactivos de WhatsApp
Un mensaje interactivo es texto del cuerpo más algo que el destinatario puede tocar: un botón WhatsApp, un menú, un enlace, una tarjeta o una solicitud de su ubicación o datos de contacto. Cuando una respuesta de plantilla obliga a interpretar texto libre, un menú WhatsApp o un conjunto de botones WhatsApp ofrece al destinatario opciones fijas y te devuelve un valor que tú definiste. Esta página cubre lo que comparten los seis tipos; la página de cada tipo cubre su forma en la red y sus propios límites.
Los seis tipos
| Tipo | Bird interactive.type | Encabezado | Pie | Máx. del cuerpo |
|---|---|---|---|---|
| Botones de respuesta | button | texto, imagen, video, documento | sí | 1024 |
| Menús de lista | list | solo texto | sí | 4096 |
| Botones de enlace | cta_url | texto, imagen, video, documento | sí | 1024 |
| Carruseles multimedia | carousel | ninguno en el mensaje; imagen o video por tarjeta | no | 1024 mensaje, 160 por tarjeta |
| Solicitudes de ubicación | location_request_message | ninguno | no | 1024 |
| Solicitudes de datos de contacto | request_contact_info | ninguno | no | 1024 |
Todos los tipos son de formato libre: solo se pueden entregar dentro de una ventana de atención al cliente abierta, y Meta nunca los revisa como lo hace con una plantilla.
Los mensajes interactivos son contenido de formato libre, por lo que se aplica la regla de la ventana de atención al cliente: consulta la ventana de atención al cliente para saber qué significa eso y qué devuelve una ventana cerrada.
Todo envío interactivo también requiere from, un número que tu espacio de trabajo posee. Los números gestionados de Bird no pueden usarlo, así que un envío interactivo necesita primero un número propio conectado.
El campo de contenido interactivo
interactive es uno de los campos de contenido mutuamente excluyentes en POST /v1/whatsapp/messages, junto a template, text, image y el resto: solo uno puede estar presente en un envío. Dentro de interactive, type indica cuál de las seis variantes es, y el campo propio de esa variante lleva el resto (buttons, list, cta_url o cards). El esquema prohíbe el campo de cualquier otra variante, así que mezclar dos variantes en un envío falla en la validación antes de llegar a un handler.
Para el sobre de la solicitud, el modelo de respuesta 202 y los reintentos seguros, consulta Envío de mensajes WhatsApp en lugar de repetirlos aquí.
Este es un mensaje interactivo mínimo: dos botones WhatsApp en un envío de botones de respuesta, un idioma a la vez.
const msg = await bird.whatsapp.send({
to: "+15551234567",
from: "+13124495648",
interactive: {
type: "button",
body_text: "Your gardening workshop is scheduled for 9am tomorrow.",
buttons: [
{ type: "quick_reply", quick_reply: { slug: "change-booking", text: "Change" } },
{ type: "quick_reply", quick_reply: { slug: "cancel-booking", text: "Cancel" } },
],
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+15551234567",
from_="+13124495648",
interactive={
"type": "button",
"body_text": "Your gardening workshop is scheduled for 9am tomorrow.",
"buttons": [
{"type": "quick_reply", "quick_reply": {"slug": "change-booking", "text": "Change"}},
{"type": "quick_reply", "quick_reply": {"slug": "cancel-booking", "text": "Cancel"}},
],
},
)
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: "+15551234567",
From: "+13124495648",
Interactive: &bird.WhatsAppInteractiveSend{
Type: "button",
BodyText: "Your gardening workshop is scheduled for 9am tomorrow.",
Buttons: &[]bird.WhatsAppInteractiveButtonSend{
{Type: "quick_reply", QuickReply: &bird.WhatsAppInteractiveQuickReplyButtonSend{Slug: "change-booking", Text: "Change"}},
{Type: "quick_reply", QuickReply: &bird.WhatsAppInteractiveQuickReplyButtonSend{Slug: "cancel-booking", Text: "Cancel"}},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$interactive = (new WhatsAppMessageSendRequestInteractive())
->setType('button')
->setBodyText('Your gardening workshop is scheduled for 9am tomorrow.')
->setButtons([
(new WhatsAppInteractiveButtonSend())
->setType('quick_reply')
->setQuickReply((new WhatsAppInteractiveButtonSendQuickReply())->setSlug('change-booking')->setText('Change')),
(new WhatsAppInteractiveButtonSend())
->setType('quick_reply')
->setQuickReply((new WhatsAppInteractiveButtonSendQuickReply())->setSlug('cancel-booking')->setText('Cancel')),
]);
$message = $bird->whatsapp->send(
to: '+15551234567',
from: '+13124495648',
interactive: $interactive,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--from +13124495648 \
--interactive '{"body_text":"Your gardening workshop is scheduled for 9am tomorrow.","buttons":[{"quick_reply":{"slug":"change-booking","text":"Change"},"type":"quick_reply"},{"quick_reply":{"slug":"cancel-booking","text":"Cancel"},"type":"quick_reply"}],"type":"button"}' \
--to +15551234567{
"name": "whatsapp_send",
"arguments": {
"from": "+13124495648",
"interactive": {
"body_text": "Your gardening workshop is scheduled for 9am tomorrow.",
"buttons": [
{
"quick_reply": {
"slug": "change-booking",
"text": "Change"
},
"type": "quick_reply"
},
{
"quick_reply": {
"slug": "cancel-booking",
"text": "Cancel"
},
"type": "quick_reply"
}
],
"type": "button"
},
"to": "+15551234567"
}
}curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"from": "+13124495648",
"interactive": {
"type": "button",
"body_text": "Your gardening workshop is scheduled for 9am tomorrow.",
"buttons": [
{ "type": "quick_reply", "quick_reply": { "slug": "change-booking", "text": "Change" } },
{ "type": "quick_reply", "quick_reply": { "slug": "cancel-booking", "text": "Cancel" } }
]
}
}'Botones
Cuatro de los seis tipos colocan un botón, y todos comparten la misma forma: un objeto discriminado cuyo type es quick_reply o cta_url, cada uno con su propio campo anidado del mismo nombre. Un botón quick_reply lleva slug y text; un botón cta_url lleva text y url. Qué tipos aceptan qué forma de botón:
- Botones de respuesta envían solo botones quick_reply, de 1 a 3.
- Botones de enlace envían exactamente un botón cta_url.
- Carruseles multimedia colocan botones en cada tarjeta: un botón cta_url o hasta tres botones quick_reply, y todas las tarjetas del carrusel deben coincidir.
- Menús de lista usan filas dentro de secciones en lugar de este objeto de botón, documentados en su propia página.
El slug de un botón quick_reply es tu propio identificador para ese botón. Nunca se muestra al destinatario, solo se muestra su etiqueta text, y el slug se devuelve tal cual en la respuesta. Ese viaje de ida y vuelta es lo que permite correlacionar una respuesta con el botón que la generó, así que vale la pena decirlo una vez aquí, en lugar de en cada página hoja.
Leer una respuesta
Al pulsar un botón o elegir una fila de menú se envía su propio mensaje entrante con un objeto interactive_reply. interactive_reply.type es button o list; en cualquier caso, el objeto anidado lleva el slug y el text que declaraste, la etiqueta que el destinatario realmente vio. Los dos tipos de solicitud, solicitudes de ubicación y solicitudes de datos de contacto, responden de forma distinta: la respuesta a una solicitud de ubicación es un mensaje entrante normal de ubicación, y la respuesta a una solicitud de datos de contacto es una tarjeta de contacto entrante, no un interactive_reply.
Una respuesta te llega a través de la lista de mensajes y GET /v1/whatsapp/messages/{id}, de la misma forma que cualquier mensaje entrante de WhatsApp. Para actuar en cuanto llega en lugar de hacer polling, suscríbete al webhook whatsapp.received: su payload lleva interactive_reply, así que ya indica el botón o la fila que se pulsó. Recibir respuestas interactivas cubre la forma de lectura de un toque, el payload del webhook y los toques que llegan en otro campo.
Citar un mensaje para correlacionar una respuesta
in_reply_to_message_id en un envío cita un mensaje anterior de la misma conversación, y cada mensaje, enviado o recibido, lo devuelve en una lectura. Es un solo campo para ambas direcciones.
La correlación que esto te da es asimétrica. Un toque en un botón WhatsApp o en una fila de menú lleva el context propio de Meta, así que in_reply_to_message_id se resuelve al mensaje que lo ofreció. Una tarjeta de contacto compartida no lleva ningún context, así que no se resuelve a nada: correlacionas la respuesta a una solicitud de datos de contacto por from y tiempo, no por este campo.
La resolución pasa por un almacén de contexto de mensajes, y un fallo omite el campo en lugar de informar uno. Eso es indistinguible, en la red, de una respuesta que no contesta a nada. Una integración que necesite correlación fiable no debería depender solo de este campo: lleva tu propio metadata en el envío y haz la coincidencia con eso.
La ventana en la que un mensaje puede citarse está limitada a 15 días; pasado ese plazo, el envío falla con un 404 E15071, porque Bird ya no conserva el id del proveedor que una cita necesita. Envío de mensajes WhatsApp documenta el campo del lado del envío: su longitud, su resolución y la forma de la solicitud.
Errores
Tres códigos de error son específicos del contenido interactivo. Cada uno solo se activa en los tipos que tienen el campo que verifica, así que la cuarta columna indica qué tipos pueden alcanzarlo.
| Código | Estado | Qué lo activa | Se aplica a |
|---|---|---|---|
| E15055 WhatsAppInteractiveLimitExceeded | 422 | El mensaje excede un límite para su tipo; más de 10 filas entre las secciones de una lista. | Solo menús de lista |
| E15056 WhatsAppInteractiveDuplicateLabel | 422 | Dos botones o filas del mismo mensaje comparten una etiqueta. | Cualquier tipo con botones o filas etiquetados: botones de respuesta, menús de lista, carruseles multimedia |
| E15059 WhatsAppInteractiveCarouselButtonsMismatch | 422 | Las tarjetas de un carrusel no llevan todas los mismos botones. | Solo carruseles multimedia |
Todo envío interactivo también puede generar los errores de cualquier envío WhatsApp: una ventana de atención al cliente cerrada, un remitente ausente o inválido, un destinatario inválido o contenido ambiguo. Son comunes a todos los tipos de contenido WhatsApp, no específicos de los mensajes interactivos; consulta Envío de mensajes WhatsApp para esa lista en lugar de copiarla aquí.
Próximos pasos
- Envío de mensajes WhatsApp: el sobre de la solicitud, el modelo 202 y los reintentos seguros
- Eventos de WhatsApp: sigue la entrega por mensaje, a través de API o webhooks
- Plantillas de WhatsApp: los mensajes que aún puedes enviar cuando la ventana está cerrada
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