Eventos de WhatsApp
Bird registra eventos para mensajes WhatsApp entrantes y salientes. Una línea de tiempo saliente muestra lo que ocurrió después de que un envío devolvió 202: aceptación, traspaso a WhatsApp, entrega, lectura o fallo. Una línea de tiempo entrante registra cuándo Bird recibió el mensaje.
La envoltura de evento
Los eventos de entrega, mensaje entrante y reacción de WhatsApp usan el sobre de webhook estándar: un type, un timestamp y un objeto data específico del tipo.
Ejemplo de código
{
"data": {
"direction": "outbound",
"from": { "phone_number": "+13124495569" },
"metadata": { "session_id": "sess_4821" },
"tags": [{ "name": "flow", "value": "login-otp" }],
"to": { "phone_number": "+14155550100" },
"whatsapp_id": "wam_01ky7qbvswf3fvyaw3az90391c",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:51:39.913Z",
"type": "whatsapp.delivered"
}Cada payload público de webhook WhatsApp de un mensaje contiene whatsapp_id, workspace_id, direction, from, to, tags y metadata. whatsapp.reacted es la excepción, porque una reacción es una anotación sobre un mensaje y no un mensaje en sí; Reacciones más abajo describe su forma. Una dirección puede incluir un phone_number E.164, un ID de usuario con alcance de negocio de Meta en bsuid, o ambos. Un mensaje recibido de un usuario de WhatsApp también incluye el perfil que publica, en username y display_name. tags y metadata son null cuando el envío no incluyó ninguno. Un mensaje enviado como respuesta también incluye in_reply_to_message_id en cada evento saliente de su línea de tiempo, desde whatsapp.accepted hasta whatsapp.read, whatsapp.failed o whatsapp.rejected, indicando el mensaje que responde.
El API de eventos devuelve registros de línea de tiempo más ligeros con un id, type y una marca de tiempo occurred_at. El ID del mensaje ya está en la URL de la solicitud.
Eventos de ciclo de vida
Los eventos aparecen en orden cronológico. Un mensaje saliente puede detenerse en whatsapp.failed o whatsapp.rejected, y su evento whatsapp.read aparece solo si el destinatario abre el mensaje. Una línea de tiempo entrante comienza con whatsapp.received y puede registrar whatsapp.read después de que tu espacio de trabajo marque el mensaje como leído.
| Evento | Significado |
|---|---|
| whatsapp.accepted | Bird aceptó la solicitud de envío. Es lo que reportó el 202. |
| whatsapp.sent | Bird entregó el mensaje a la red de WhatsApp. |
| whatsapp.delivered | WhatsApp confirmó la entrega al dispositivo del destinatario. |
| whatsapp.read | El destinatario abrió el mensaje. |
| whatsapp.failed | El mensaje no fue entregado. error.code indica qué lo impidió. |
| whatsapp.rejected | Bird rechazó el mensaje antes de enviarlo. No se cobró. |
| whatsapp.received | Bird recibió un mensaje entrante de un contacto. |
Los callbacks delivered o read aplicables pueden activar la parte del precio correspondiente a Meta. Los payloads de eventos WhatsApp no incluyen datos sobre el costo. Vuelve a leer el mensaje con GET /v1/whatsapp/messages/{message_id} para ver cuánto costó. Consulta Costo y facturación.
Marcar un mensaje entrante como leído registra whatsapp.read en su línea de tiempo, pero no emite un webhook de confirmación de lectura. El mensaje entrante mantiene su estado received y registra read_at después de que WhatsApp acepta la confirmación.
whatsapp.read no cambia el status del mensaje. Un mensaje entregado permanece como delivered; el mensaje también registra la lectura en read_at.
whatsapp.delivered se puede omitir por completo. Cuando el destinatario ya tiene el chat abierto en su dispositivo, Meta reporta la lectura sin reportar nunca una entrega, así que la línea de tiempo queda whatsapp.accepted → whatsapp.sent → whatsapp.read sin whatsapp.delivered entre ellos. Trata read como prueba de entrega: un consumidor que espera delivered antes de considerar que el mensaje llegó se quedará esperando precisamente a los destinatarios que lo vieron más rápido, y uno que calcule una tasa de entrega solo con delivered la subreporta. El status del mensaje permanece como sent en este caso, ya que solo un acuse de entrega lo avanza.
Un callback de solo lectura aún puede activar la tarifa aplicable de Meta. Bird usa una misma identidad de cobro en las rutas de entrega y lectura; la ausencia del evento de entrega no implica un componente Meta gratuito. Consulta Costo y facturación.
La lista de tipos de evento es abierta: se pueden agregar nuevos tipos con el tiempo, así que trata un valor no reconocido como un evento futuro en lugar de un error.
Eventos de fallo
whatsapp.failed y whatsapp.rejected son terminales. Un rechazo significa que Bird detuvo el mensaje antes de enviarlo a WhatsApp, así que no se cobró. Las causas incluyen un destinatario suprimido o dado de baja, saldo insuficiente en la billetera o un destino sin precio configurado. Un fallo significa que el mensaje no se entregó, y error.code indica quién lo decidió. La mayoría de los códigos contienen el veredicto de WhatsApp, mapeado desde el código que reportó. internal_error es la excepción: registra la falta de una credencial de envío utilizable o reintentos de procesamiento agotados. Un intento de transporte incierto no prueba que Meta nunca recibió la solicitud. meta_error_code contiene el código de WhatsApp cuando está disponible, y un fallo internal_error no tiene ninguno por definición.
Ambos eventos contienen un objeto error con un Bird code estable, un description legible, un meta_error_code opcional y occurred_at. El objeto aparece en los registros de API y en los payloads de webhook solo para estos tipos de evento.
Eventos de reacción
Una reacción con emoji anota un mensaje existente. No crea un mensaje whatsapp.received. Bird emite whatsapp.reacted cuando un contacto agrega, cambia o elimina una reacción, como se describe en Reacciones. Las reacciones enviadas por tu número de negocio no emiten ese webhook. Consulta Enviar reacciones para agregar, reemplazar o eliminar tu reacción, y Recibir reacciones para ejemplos de webhook y REST API. Las reacciones de contactos no abren una ventana de servicio al cliente.
El registro de reacciones del mensaje reaccionado registra los cambios tanto del contacto como de tu número de negocio: adiciones, reemplazos y eliminaciones.
Hay un caso que no se registra en ningún lado. Bird asocia una reacción con su mensaje a través de un ID de proveedor que conserva durante 15 días, mientras que WhatsApp acepta una reacción en un mensaje de hasta 30 días de antigüedad, así que una reacción sobre un mensaje más antiguo no se puede asociar y no llega ni al registro ni a reactions. Por lo tanto, un mensaje sin entradas no es prueba de que nadie reaccionó a él.
Lee ese registro con GET /v1/whatsapp/messages/{message_id}/reaction-events, del más reciente al más antiguo. Una entrada nombra el emoji, quién hizo el cambio y un status de received, sent, failed o rejected; una entrada failed o rejected incluye la razón en error. Cada entrada tiene un ID de reacción (war_…) y una marca de tiempo occurred_at. Una eliminación tiene emoji: null. Los cambios pendientes no tienen entrada hasta que se conoce su resultado. Una reacción nunca se cobra, así que ningún fallo en ella es de facturación. Para ver lo que actualmente está vigente en el mensaje en lugar del historial de cambios, lee su reactions con GET /v1/whatsapp/messages/{message_id}, que reduce el registro a una entrada por remitente.
Eventos de supresión
Más allá del ciclo de vida por mensaje, un evento reporta un cambio en la lista de supresión del espacio de trabajo: whatsapp_suppression.created se dispara cuando se abre una supresión. El payload lleva el suppression_id, el address suprimido en formato E.164, el waba al que se limita el bloqueo (null cuando cubre todo el espacio de trabajo, sin importar qué cuenta envíe), el reason y el workspace_id, para que tu propio sistema pueda ver nuevos bloqueos sin hacer polling. Solo las aperturas disparan un evento: finalizar una supresión aún no lo hace, así que vuelve a leer la lista antes de asumir que un bloqueo replicado sigue vigente:
Ejemplo de código
{
"type": "whatsapp_suppression.created",
"timestamp": "2026-08-25T14:52:03.192524705Z",
"data": {
"suppression_id": "was_01krdgeqcxet5s7t44vh8rt9mg",
"address": "+14155550100",
"waba": null,
"reason": "manual",
"workspace_id": "ws_01ky7m21hjffh9s8kq76gyb1xf"
}
}Una baja que el destinatario declaró por sí mismo es una preferencia en lugar de una supresión, y dispara preference.revoked en su lugar.
Leer eventos desde el API
GET /v1/whatsapp/messages/{message_id}/events devuelve la línea de tiempo en orden cronológico. La lista acotada no está paginada. Leer eventos requiere una clave API con whatsapp:read:
const { data } = await bird.whatsapp.listEvents("wa_abc123");
for (const event of data) console.log(event.type, event.occurred_at);events = client.whatsapp.list_events("wa_abc123")
for event in events.data:
print(event.type, event.occurred_at)events, err := client.Whatsapp.ListEvents(context.Background(), "wam_01krdgeqcxet5s7t44vh8rt9mg", bird.WhatsappListEventsParams{})
if err != nil {
log.Fatal(err)
}
for _, e := range events.Data {
fmt.Println(e.Id, e.Type)
}$events = $bird->whatsapp->listEvents('wamid_01krdgeqcxet5s7t44vh8rt9mg');
foreach ($events->getData() ?? [] as $event) {
echo $event->getType(), ' ', $event->getId(), "\n";
}bird whatsapp list-events <message-id>curl https://us1.platform.bird.com/v1/whatsapp/messages/wam_.../events \
-H "Authorization: Bearer $BIRD_API_KEY"Un mensaje que fue aceptado, enviado, entregado y leído devuelve cuatro eventos:
Ejemplo de código
{
"data": [
{
"id": "ev_01ky7q6a1fejfbvs0myn41hj41",
"occurred_at": "2026-07-23T14:48:34.71Z",
"type": "whatsapp.accepted"
},
{
"id": "ev_01ky7q6a2denvtd6jg1vqwmg13",
"occurred_at": "2026-07-23T14:48:35.671Z",
"type": "whatsapp.sent"
},
{
"id": "ev_01ky7q6a2zff9r2qm74mmg1g6z",
"occurred_at": "2026-07-23T14:48:36.642Z",
"type": "whatsapp.delivered"
},
{
"id": "ev_01ky7q6c21frssf0vj8h50qysw",
"occurred_at": "2026-07-23T14:48:38.65Z",
"type": "whatsapp.read"
}
]
}Pasa type para obtener un tipo exacto de evento público, como ?type=whatsapp.failed o ?type=whatsapp.read. Omítelo para la línea de tiempo completa.
Esta misma línea de tiempo es la que la página del registro de WhatsApp muestra cuando abres un mensaje.

Webhooks
Suscríbete a whatsapp.accepted, whatsapp.sent, whatsapp.delivered, whatsapp.read, whatsapp.failed, whatsapp.rejected, whatsapp.received y whatsapp.reacted desde la página Webhooks o la API de webhooks. La guía de webhooks cubre endpoints, firmas y reintentos.
whatsapp.received incluye el contenido del mensaje además del sobre descrito arriba, para que un endpoint pueda actuar sobre un mensaje entrante sin volver a leerlo. Un toque en un mensaje interactivo llega como interactive_reply, y in_reply_to_message_id nombra el mensaje que responde:
Ejemplo de código
{
"data": {
"direction": "inbound",
"from": {
"display_name": "Alex Rivera",
"phone_number": "+14155550100",
"username": "alexr"
},
"in_reply_to_message_id": "wam_01ky7qbvswf3fvyaw3az90391c",
"interactive_reply": {
"list": {
"description": "Next day to 2 days",
"slug": "priority_express",
"text": "Priority Mail Express"
},
"type": "list"
},
"metadata": null,
"tags": null,
"to": { "phone_number": "+13124495569" },
"whatsapp_id": "wam_01ky8b3xq4gd7pmzn2ka51f7te",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:52:04.118Z",
"type": "whatsapp.received"
}Las otras ramas de contenido siguen la misma forma de uno-de-estos: text, image, video, audio, sticker, document, location, contact_cards y unsupported para un tipo que el API no modela. GET /v1/whatsapp/messages/{message_id} documenta cada una.
Reacciones
whatsapp.reacted se dispara cuando un usuario de WhatsApp reacciona a uno de tus mensajes. Es el único evento WhatsApp que no forma parte de la línea de tiempo de entrega de un mensaje: no aparece en GET /v1/whatsapp/messages/{message_id}/events, y no hay nada con lo que filtrarlo allí.
whatsapp_id nombra el mensaje al que se reaccionó, no la reacción, y emoji es el cambio que hizo el usuario. Un usuario que reacciona, cambia su emoji y luego retira la reacción produce tres eventos en ese mismo mensaje. WhatsApp no envía una eliminación entre los dos primeros, así que un cambio llega como un solo evento con el nuevo emoji.
Ejemplo de código
{
"data": {
"emoji": "👍",
"from": { "phone_number": "+14155550100" },
"to": { "phone_number": "+13124495569" },
"whatsapp_id": "wam_01ky8b3xq4gd7pmzn2ka51f7te",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
},
"timestamp": "2026-07-23T14:52:11.000Z",
"type": "whatsapp.reacted"
}WhatsApp reporta la hora de la reacción al segundo, así que dos de esos tres eventos pueden compartir un mismo timestamp. Ordenarlos por él no los secuenciará, y tampoco lo hará el orden de entrega, que los reintentos hacen poco fiable. Actúa sobre la reacción que cada evento lleva, como el cambio que describe. No reconstruyas la secuencia a partir de los eventos ni trates el último en llegar como la reacción vigente del mensaje, porque ni las marcas de tiempo ni el orden de llegada lo respaldan. Lee el mensaje de vuelta para ver las reacciones vigentes: GET /v1/whatsapp/messages/{message_id} devuelve una entrada por remitente en reactions, y el registro de reacciones del mensaje tiene cada cambio.
emoji está presente y es null cuando el usuario retiró su reacción, así que un null es la eliminación en sí y no un valor ausente. El emoji se entrega exactamente como WhatsApp lo envió y no se normaliza, así que ❤ y ❤️ te llegan como cadenas diferentes.
Próximos pasos
- Recibir reacciones: gestiona webhooks de reacción de contactos y lee las reacciones actuales
- Enviar reacciones: agrega, reemplaza o elimina tu reacción
- Marcar mensaje como leído: confirma la recepción de un mensaje entrante y muestra indicador de escritura
- Registro de WhatsApp: la vista por mensaje que muestra esta línea de tiempo
- Enviar mensajes WhatsApp: donde comienza el ciclo de vida de un mensaje
- Guía de webhooks: endpoints, firmas, reintentos y el catálogo completo de eventos
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