Skip to main content

Mensajes de Plantilla

Cuando necesitas escribirle primero a un cliente — recuperar un carrito abandonado, avisar que un pedido está listo, entregar un PIN de retiro — el mensaje tiene que salir como una plantilla aprobada por Meta. Estos endpoints son la puerta pública para pedir ese envío y para consultar dónde quedó.
Tu cuenta tiene que estar habilitada antes de la primera llamada. El contrato se publica por adelantado para que puedas construir; la habilitación es por cuenta y la coordinamos contigo junto con la plantilla y el número remitente.Mientras tu cuenta no esté habilitada, cada llamada responde 422 con "type": "account_not_eligible", siempre y no de forma intermitente. No es un problema de tu request y ningún reintento lo cambia: es configuración de nuestro lado, y la respuesta lo dice con esas palabras en vez de esconderlo detrás de un error genérico. Escríbenos para habilitarla. Cualquier cambio al contrato se anuncia en el Changelog.

Qué hace y qué no hace

Requisitos previos

Para saber qué plantillas puedes enviar, consulta el catálogo. GET /v1/templates lista las que tienes aprobadas, con el nombre y el idioma exactos que van en esta llamada. Una lista vacía significa que todavía no hay ninguna aprobada, y hasta que la haya cada envío se rechaza. El campo sendable de cada plantilla dice si esta API puede enviarla: es false cuando el texto mezcla variables por posición y por nombre, las numera con huecos o lleva una variable en el encabezado (esta API rellena solo el cuerpo). Una plantilla con sendable: false responde 422 al enviar, y el arreglo está en Meta, no en tu request.

Enviar un mensaje

Campos del request

El identificador de tu empresa no viaja en el request: se deriva de tu API key (Autenticación). La plantilla sí viaja, por nombre e idioma, y se valida contra las que tienes aprobadas.

Respuestas

Envío aceptado — 202

Validamos, reservamos el event_id y lo entregamos al servicio de envío. La respuesta llega apenas eso ocurre y no trae el desenlace:
accepted significa “va en camino”, no “salió bien”. El envío sigue su curso después de la respuesta. No lo leas como éxito — consulta el estado, que ahí sí se resuelve contra el mensaje real. Es el único camino: los webhooks de /v1/webhooks sólo emiten eventos de pedido (order.created, order.status_changed), ninguno de entrega.
Configura el timeout de tu cliente en 35 segundos o más. El POST espera la llamada al servicio de envío, que tiene su propio tope de 30 segundos. En el camino normal responde en menos de un segundo, pero eso es el caso típico y no una garantía: un cliente con timeout de 5 o 10 segundos abandona requests sanos que siguen en vuelo, y después ve fallas que nunca ocurrieron.

Sin envío nuevo — 200

Un 200 significa que esta petición no produjo un envío. Son dos casos:
  • Un event_id repetido: devuelve el estado guardado de la primera petición (Idempotencia).
  • Un envío inviable de entrada — el aviso está vencido, la conversación del cliente está en pausa, o el comercio apagó el envío desde el panel: responde "status": "discarded" con el motivo en status_detail (stale, paused o disabled). La petición funcionó; el envío está apagado a propósito.

Idempotencia

Mismo event_id = mismo envío. Reenviarlo devuelve el estado de la primera petición y nunca genera un segundo WhatsApp. Por eso es seguro reintentar ante un timeout de red. La idempotencia corre antes de las validaciones de teléfono, plantilla y frescura: un reenvío devuelve el estado guardado sin volver a juzgar esos datos. Lo que sí se evalúa primero es la forma del cuerpo — el charset del event_id, los largos de template.name y template.language, que occurred_at parsee. Un reenvío mal formado recibe 422 y nunca llega a la idempotencia. Hay un caso en que un event_id deja de servir: si del lado del envío ya quedó atado a otro mensaje, o salió de la ventana de 30 días, la respuesta es 409 con "type": "idempotency_conflict". Manda ese evento con un event_id nuevo — repetir el mismo devuelve el mismo 409. A propósito no guardamos estado bajo la clave rechazada: si lo hiciéramos, el siguiente intento recibiría un reenvío silencioso en vez del 409 que te dice qué corregir.
Un 422 en un reenvío merece una mirada antes de descartar el evento. Si el primer intento pudo haber salido, el evento no está perdido: corrige la forma del cuerpo y reenvía con el mismo event_id, que sigue siendo seguro y devuelve el estado real. Cuando el 422 fue de cuenta, ese reenvío responde 200 con "status": "failed" — es el estado guardado del primer intento, no un envío nuevo.
El event_id no es un identificador de traza. Si tu sistema genera un id nuevo por cada intento HTTP, ese valor no sirve como event_id: cada reintento se vería como un evento distinto y el cliente recibiría el WhatsApp más de una vez. Para rastrear intentos usa el header X-Request-ID — ver Request IDs.

Consultar el estado

“Enviado” no es “entregado”: Meta reporta la entrega al teléfono minutos u horas después — o nunca, si el teléfono está apagado. Ese ciclo se consulta por tu event_id:
Un event_id que nunca se envió responde 404; uno que no respeta el charset responde 422.
status_detail en un failed no es una lista cerrada y puede faltar. Puede ser un rechazo de cuenta (account_not_eligible, no_sender_number), un envío que no llegó a completarse (send_failed) o que se canceló antes de intentarse (send_cancelled), o, cuando el proveedor rechaza la entrega con un código, provider_error_<código> — por ejemplo provider_error_131049. El código es el de Meta y su catálogo cambia sin que nos avisen, así que no ramifiques de forma exhaustiva sobre este campo: decide por status, registra status_detail y trata cualquier valor desconocido —o ausente— como un fallo genérico.Un accepted también puede traer delivery_not_observable: el envío de ese comercio vive en una instancia propia que este despliegue no puede leer, así que el estado no se resolverá en cada consulta — no es un fallo.
Dos detalles útiles al integrar:
  • updated_at es cuándo cambió por última vez el registro, no cuándo consultaste. Consultar no lo mueve, pero una escritura de entrega que deja el status igual sí lo mueve, así que no lo uses como marca de agua: reconcilia por event_id.
  • Sólo failed y discarded son definitivos. sent, delivered y read se vuelven a resolver en cada consulta, porque todavía puede llegar un recibo posterior — incluso uno de fallo después de un read.

Ventanas de tiempo

Manda el offset en occurred_at. Un valor sin zona horaria se interpreta como UTC. Si tu sistema envía hora local de Perú (UTC-5), el evento se envejece 5 horas de más contra una ventana de 6: un carrito abandonado hace dos horas computa como de siete y sale discarded por stale — con respuesta 200, sin error y sin mensaje enviado.

Errores

Los errores siguen el formato estándar de la API:
Los dos 422 de cuenta se distinguen por detail[0].type, no por el campo: llegan con "loc": ["body"] porque no hay un valor que corregir. Es el mismo formato de array que cualquier otro 422 — ver Manejo de Errores. El 409, en cambio, usa el formato estándar y trae su type en la raíz.
Un 502 ya no significa “puede ser cualquier cosa”. Los rechazos que dependen de tu cuenta o de tu evento tienen su propia respuesta: cuenta no habilitada y falta de número remitente son 422, una conversación en pausa es 200 con "status": "discarded" y "status_detail": "paused", y una clave repetida es 409. Lo que queda en 502 es una falla real del servicio interno de envío. Reintenta con backoff unas pocas veces; si persiste, deja de reintentar y escríbenos.
Ni un aviso vencido, ni una conversación en pausa, ni el envío apagado son errores: responden 200 con "status": "discarded" y "status_detail" en "stale", "paused" o "disabled". Una pausa la pone un agente humano que tomó la conversación, o un corte por facturación; el apagado lo pone el comercio desde el panel de carrito abandonado. Ninguno de los dos se levanta desde el request.

Receta: Shopify (Flow)

Shopify Flow hace este mismo POST sin código y sin un servidor propio en medio: es el ejemplo guiado del contrato de arriba, con el disparo del abandono ya resuelto. ¿Usas Shopify? Sigue la receta guiada →

Probar la integración

No existe un ambiente sandbox. El dashboard crea siempre keys de tipo live y no hay un entorno aislado detrás de ellas — ver Autenticación. El ambiente de prueba de esta integración se diferencia por el teléfono destino, no por la credencial.
  • Fase de prueba — envía a teléfonos de tu propio equipo, en una ventana acordada. El mensaje llega de verdad, con la plantilla real: esa es exactamente la prueba.
  • Fase productiva — misma key, mismo contrato; solo cambia que los teléfonos son de clientes reales.
La lista de teléfonos destino es entonces el único control real entre una fase y la otra: acuérdala por escrito antes de la primera prueba y trátala como parte de la configuración de la integración.