Skip to main content

POST /messages

Enviar un mensaje de WhatsApp. Soporta texto, imagen, video, audio, documento, template e interactivos. Scope requerido: messages:write

Body

Enviar texto

Los mensajes de texto libre requieren una ventana de servicio activa (24hs desde el último mensaje del contacto). Si la ventana expiró, usá un template. Ver Ventana de servicio.

Enviar template

Enviar imagen

Response 201

waMessageId es null en el response inicial. El envío a Meta es asíncrono — status: "queued" significa que aceptamos la request y la encolamos. Para obtener el waMessageId real (el wamid.HBgL... de Meta) tenés dos opciones:
  1. Recomendado: suscribite al webhook message.status_changed. Cuando el status pasa a sent, el payload trae waMessageId poblado. Es la forma confiable.
  2. Polling: GET /messages/{data.id} después de 1-2 segundos.
Para correlacionar tu mensaje con el de Meta inmediatamente, guardá el data.id (UUID interno) que sí está disponible al instante.

Idempotencia

Incluí el header Idempotency-Key para evitar envíos duplicados. Ver Idempotencia.

Errores frecuentes

Comportamiento

  • Si el contacto no existe, se crea automáticamente con el número.
  • Si no hay conversación previa, se crea una nueva.
  • El mensaje se encola y se envía de forma asíncrona. El status inicial es queued.
  • Consultá el estado final con GET /messages/:id.

POST /messages/batch

Encolar hasta 100 mensajes en una sola llamada. Pensado para envíos masivos (avisos, solicitudes de reseña, recordatorios). Cada item se acepta, saltea o rechaza de forma independiente — un destinatario inválido nunca tumba el lote. Scope requerido: messages:write (el mismo que POST /messages — cualquier API key que ya envía mensajes sirve sin cambios). Responde 202 Accepted apenas persiste y encola; la entrega ocurre de forma asíncrona al ritmo de envío de tu cuenta. Los envíos masivos usan prioridad de campaña, así que nunca le quitan turno al chat en vivo.

Body

El campo raíz para el canal compartido se llama defaultPhoneNumberId (no phoneNumberId). Por item, el campo sí es phoneNumberId y, si lo omitís, hereda el defaultPhoneNumberId.

Ejemplo — solicitudes de reseña

Response 202

Trae un detalle por item, en el mismo orden en que los mandaste:
Cada item del array items tiene uno de tres resultados:

Reconciliación recomendada

Mandá clientRef con tu propio identificador (número de pedido, ID de solicitud) en cada item. Después suscribite al webhook message.status_changed: cada cambio de estado (sentdeliveredread o failed) trae tu clientRef de vuelta en data.clientRef, así matcheás los eventos contra tus registros sin guardar ningún mapping.
Para volúmenes mayores a 100, mandá varias requests (por ejemplo en tandas de 100). El rate limit de la API v2 aplica por request, no por mensaje — ver Rate limits.

Errores frecuentes

Los errores por item (teléfono o canal inválido, campo faltante) no devuelven un código de error global: vienen dentro de items[].error con result: "failed".

GET /messages/:id

Consultar un mensaje por ID. Útil para verificar el estado de envío. Scope requerido: messages:read

Response 200

Para entender los estados posibles, ver Estados de mensajes.

POST /messages/:waMessageId/read

Marcar un mensaje entrante como leído por su waMessageId (el mismo que llega en el webhook message.received). Conveniente cuando tenés a mano el waMessageId en lugar del conversationId. Scope requerido: conversations:write
Equivalente a POST /conversations/:id/read — manda el read receipt a Meta y resetea el unreadCount de la conversación padre.
404 si el waMessageId no existe o no es inbound.