Estados de mensajes en la API de Waspy: queued, sent, delivered, read y failed
El POST deja el mensaje en queued. Meta lo pasa a sent, delivered, read o failed. Lo leés en GET /messages/:id y en el webhook message.status_changed. clientRef vuelve igual.
Respuesta corta: POST /api/v2/messages responde 201 con status: "queued". Después el mensaje pasa a sent, delivered, read, o a failed. Lo consultás con GET /api/v2/messages/:id (scope messages:read) o con el webhook message.status_changed. Si mandaste clientRef, vuelve igual en los dos.
El ciclo está en estados de mensajes. El sobre del webhook está en eventos. El campo en el POST está en messages. Cómo dar de alta la suscripción está en gestionar webhooks.
Los cinco estados
| Estado | Qué pasó |
|---|---|
queued | Waspy aceptó el pedido y lo encoló. waMessageId es null |
sent | Meta aceptó el mensaje. Ahí aparece el waMessageId (wamid...) |
delivered | Llegó al teléfono |
read | La persona lo leyó. Solo si tiene las confirmaciones de lectura activadas |
failed | No salió o no se entregó. Mirá errorCode y errorMessage |
curl -sS "https://api.waspytech.com/api/v2/messages/MSG_UUID" \
-H "Authorization: Bearer $WASPY_API_KEY"
Si el UUID no es de tu cuenta, es 404 MESSAGE_NOT_FOUND. El clientRef va en data.clientRef. La página de conceptos no lo muestra en el ejemplo corto; el serializer del GET sí lo incluye.
No hagas un loop cerrado contra ese GET. Cada lectura cuenta para las 120 por minuto de la cuenta. El webhook es el camino. El límite está en rate limits.
El webhook
message.status_changed llega en un mensaje saliente. El data trae el mensaje y, además, previousStatus y newStatus. clientRef es el string que pusiste en el POST o en cada ítem de POST /messages/batch. Si no mandaste uno, es null.
El recorrido para reconciliar un pedido sin guardar una tabla de ids está en clientRef.
Los eventos pueden llegar desordenados. El procesador se queda con el estado más avanzado:
| Llega | Si ahora está | Qué hace |
|---|---|---|
delivered | sent | Avanza |
read | delivered | Avanza |
sent | delivered o read | Lo ignora |
failed | cualquiera de esos | Pasa a failed |
sent | failed | Lo ignora. No borra el errorCode |
read no implica que la persona haya respondido. La ventana de 24 horas la abre el mensaje de ella, no el tilde azul.
Códigos que vas a ver en failed
errorCode es texto con el número de Meta. La página de conceptos hoy dice que 131047 es “el número no tiene WhatsApp” y que 131056 es “el negocio no puede enviar”. El catálogo que usa el envío dice otra cosa. Para decidir un reintento, manda el catálogo. La lista oficial de Meta está en error codes.
| Código | Qué es en el catálogo del producto | ¿Se reintenta solo? |
|---|---|---|
131047 | Pasaron más de 24 horas desde el último mensaje de la persona. Hace falta una plantilla | No. Queda failed |
131026 | No se pudo entregar. El número puede no tener WhatsApp o ser inválido | No |
132000 | La cantidad de parámetros no coincide con la plantilla | No |
131056 | Demasiados mensajes entre este número y ese contacto | Sí. Sigue en queued y la cola reintenta |
Un failed no se cobra: Meta cobra el mensaje entregado. En Argentina, desde el 1 de octubre de 2026, una utilidad entregada sale alrededor de ARS 37,68 (~US$0,026). La rate card es la fuente.
clientRef e Idempotency-Key
| Header o campo | Para qué | Tope |
|---|---|---|
Idempotency-Key | Que un reintento del POST no mande dos veces. Dura 24 horas | 256 caracteres |
clientRef | Que el webhook y el GET te devuelvan tu id (pedido, ticket) | 256 caracteres |
sent y failed contra tu base cuando el 201 ya volvió. No son intercambiables: una clave de idempotencia repetida no reescribe el estado, devuelve el 201 guardado.
La API pública está en Scale y Enterprise. Diez días sin tarjeta, en el registro. La referencia está en la página de la API y en Planes.
