Sign inGet started

Recibir mensajes de WhatsApp

Los mensajes entrantes llegan al mismo recurso que los salientes, sin un endpoint de bandeja de entrada separado que consultar. Léelos con la lista de mensajes en el dashboard, o a través de la API con GET /v1/whatsapp/messages/{id} tras filtrar la lista a entrantes.
Cada mensaje entrante extiende la ventana de servicio al cliente a 24 horas después de la marca de tiempo de ese mensaje, que es lo que hace entregable una respuesta libre de tu parte. Un mensaje que llega tarde a Bird trae la ventana que su contacto realmente otorgó, y una fecha límite posterior que ya esté registrada nunca se acorta.

Qué contiene un mensaje entrante

Cada mensaje entrante comparte un sobre: un id, direction: "inbound", el contacto en from, tu propio número en to, un status de received y un created_at. Exactamente un campo de contenido lo acompaña, indicando lo que el contacto envió:
Ejemplo de código
{
  "id": "wam_01kya19eknftrs2s6p82asmvnh",
  "direction": "inbound",
  "from": { "phone_number": "+14155550100" },
  "to": { "phone_number": "+13124495569" },
  "status": "received",
  "text": { "body": "Is my order out for delivery yet?" },
  "created_at": "2026-08-25T09:04:11Z"
}
from identifica al contacto según la identidad que WhatsApp reporta: un phone_number E.164, un bsuid, o ambos, más el username y display_name que publican. Un contacto que ha adoptado un nombre de usuario de WhatsApp puede contactarte sin número de teléfono; consulta IDs de usuario con alcance de negocio para saber qué almacenar y cómo solicitar un número.
Uno de estos campos de contenido, un arm, contiene lo que enviaron, y exactamente un arm está presente en cada mensaje. Cada uno tiene su propia página, con la forma de lectura, el payload de whatsapp.received y qué tener en cuenta:
CampoQué contiene uno entrante
textbody, el mensaje que el contacto escribió
imageUn id, url, mime_type y cualquier caption
videoLos mismos campos multimedia, más cualquier caption
audioLos mismos campos multimedia, más voice en una nota de voz; no existe caption
stickerLos mismos campos multimedia, más animated
documentLos mismos campos multimedia, más cualquier filename y caption
locationlatitude y longitude, y a veces name, address o un url
contact_cardsUna o más tarjetas de contacto que el contacto compartió
interactive_replyEl slug y text del botón o fila que el contacto tocó
unsupportedEl tipo de contenido WhatsApp que API no modela, como un pedido
Dos toques llegan en un arm que quizá no esperes. Una solicitud de ubicación responde como un location entrante normal, y una solicitud de información de contacto responde como contact_cards, así que una integración que solo vigile interactive_reply para detectar un toque se pierde ambas.

Descargar multimedia entrante

Un image, video, audio, sticker o document entrante llega como una referencia a un archivo que Bird almacenó, no como el archivo en sí:
Ejemplo de código
{
  "image": {
    "id": "waf_01kyb2m4xq7whs0d8n3prv6tez",
    "url": "https://platform.bird.com/v1/whatsapp/messages/wam_01kya19eknftrs2s6p82asmvnh/media/waf_01kyb2m4xq7whs0d8n3prv6tez",
    "mime_type": "image/jpeg",
    "caption": "Is this the right part?"
  }
}
Descarga los bytes con el método de multimedia del canal, pasando el id del mensaje y el id del multimedia:
const media = await bird.whatsapp.messages.media(
  "wam_01kya19eknftrs2s6p82asmvnh",
  "waf_01kyb2m4xq7whs0d8n3prv6tez",
);
console.log(media.contentType, media.contentLength);
Recibes los bytes con el mime_type de almacenamiento declarado para ellos. Un id que Bird no reconoce devuelve 404, y un mensaje saliente no tiene multimedia almacenado que servir.
Un mensaje se puede leer durante 30 días después de su llegada, y su multimedia nunca lo sobrevive. Este endpoint lee el mensaje antes de servir el archivo, así que una vez pasada esa ventana ambos responden 404. Almacena cualquier archivo que necesites por más tiempo mientras el mensaje aún sea legible. El único caso que termina antes es que los bytes almacenados desaparezcan antes de que se cumpla la ventana: la descarga responde 410 E15021, y el mensaje aún se puede leer con el mime_type y caption del multimedia.
Internamente, este endpoint responde 302 con una URL prefirmada válida por 15 minutos. Los SDKs y la CLI se encargan de esa redirección por ti. Si lo llamas directamente, la URL prefirmada lleva su propia credencial, así que la solicitud redirigida no debe enviar también tu encabezado Authorization. Enviar ambos falla. curl -L elimina el encabezado en una redirección a otro host por sí solo; un cliente que reenvía encabezados tal cual necesita descargar el Location como una solicitud separada y sin autenticación.

Respuestas citadas

in_reply_to_message_id indica el mensaje al que responde uno entrante, cuando WhatsApp lo marca como respuesta:
Ejemplo de código
{
  "id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
  "direction": "inbound",
  "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
  "text": { "body": "Yes, that one" }
}
WhatsApp no marca todas las respuestas, y una sin marcar no lleva ningún ID. El campo también se omite cuando el mensaje citado no puede asociarse con uno que Bird tenga: un mensaje enviado antes de que este espacio de trabajo comenzara a registrarlos, o uno que supere los 15 días que Bird conserva los IDs de mensaje propios de WhatsApp. Una falta de coincidencia omite el campo en lugar de reportar uno, lo cual se lee igual que una respuesta que no contesta a nada.
Trata el campo como una pista, no como una clave. metadata en tu propio envío no ayuda aquí, porque permanece en tu mensaje y nunca viaja a la respuesta del contacto, así que una integración que necesite saber a qué pregunta pertenece una respuesta debe rastrear por sí misma la última pregunta que envió a ese contacto. Consulta Citar un mensaje para el lado de envío.

El webhook

Suscríbete a whatsapp.received para actuar sobre un mensaje entrante en cuanto llega, en lugar de consultar la lista. El payload incluye el contenido sobre el sobre del evento, así que un endpoint no necesita una lectura adicional:
Ejemplo de código
{
  "type": "whatsapp.received",
  "timestamp": "2026-08-25T09:04:11.118Z",
  "data": {
    "whatsapp_id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
    "workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
    "direction": "inbound",
    "from": { "phone_number": "+14155550100", "display_name": "Alex Rivera" },
    "to": { "phone_number": "+13124495569" },
    "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
    "interactive_reply": {
      "type": "button",
      "button": { "slug": "cancel-booking", "text": "Cancel" }
    },
    "tags": null,
    "metadata": null
  }
}
Consulta eventos de WhatsApp para ver el sobre completo y el resto de la lista de eventos.

Próximos pasos