Todo webhook entregado por Waspy comparte el mismo envelope:
{
"event": "message.received",
"webhookId": "uuid-de-tu-suscripcion",
"deliveredAt": "2026-04-19T12:34:56.789Z",
"attempt": 1,
"data": { /* específico del evento — ver abajo */ }
}
Headers HTTP en cada delivery:
Content-Type: application/json
User-Agent: Waspy-Webhooks/1.0
X-Waspy-Signature: t=1745067296,v1=<hmac-hex>
X-Waspy-Webhook-Id: <uuid>
X-Waspy-Webhook-Event: message.received
X-Waspy-Webhook-Version: v1
X-Waspy-Webhook-Attempt: 1
message.received
Llega cada vez que un contacto te envía un mensaje (texto, media, ubicación, etc).
{
"event": "message.received",
"webhookId": "...",
"deliveredAt": "2026-04-19T12:34:56.789Z",
"attempt": 1,
"data": {
"id": "uuid-mensaje-waspy",
"conversationId": "uuid-conversacion",
"contactId": "uuid-contacto",
"phoneNumberId": "uuid-canal",
"direction": "inbound",
"type": "text",
"content": { "body": "Hola, tienen stock?" },
"status": "delivered",
"waMessageId": "wamid.HBgL...",
"errorCode": null,
"errorMessage": null,
"origin": "inbound",
"sentByAi": false,
"campaignId": null,
"mediaId": null,
"mediaKey": null,
"createdAt": "2026-04-19T12:34:55.000Z",
"contact": {
"id": "uuid-contacto",
"name": "María Pérez",
"phoneNumber": "+5491126032641"
}
}
}
Para mensajes con media (imagen, audio, video, documento), mediaId y mediaKey se completan cuando termina la descarga interna desde Meta. Resolvé mediaId a una URL firmada con GET /media/:id. Ver Media.
message.status_changed
Llega cuando cambia el estado de un mensaje saliente que vos enviaste.
{
"event": "message.status_changed",
"webhookId": "...",
"deliveredAt": "2026-04-19T12:36:01.000Z",
"attempt": 1,
"data": {
"id": "uuid-mensaje",
"conversationId": "uuid-conversacion",
"contactId": "uuid-contacto",
"phoneNumberId": "uuid-canal",
"direction": "outbound",
"type": "text",
"content": { "body": "Tu pedido #1847 fue despachado." },
"status": "delivered",
"waMessageId": "wamid.OUT...",
"errorCode": null,
"errorMessage": null,
"origin": "external_api",
"sentByAi": false,
"campaignId": null,
"mediaId": null,
"mediaKey": null,
"clientRef": "resena_12345",
"createdAt": "2026-04-19T12:35:00.000Z",
"previousStatus": "sent",
"newStatus": "delivered"
}
}
Ciclo de estados: queued → sent → delivered → read o failed.
clientRef se devuelve verbatim si lo enviaste al crear el mensaje (en POST /messages o POST /messages/batch). Es la forma recomendada de reconciliar envíos masivos contra tus propios IDs sin guardar un mapping: matcheás data.clientRef y aplicás data.newStatus. Si no enviaste clientRef, llega como null.
Cuando un mensaje rebota (newStatus: "failed"), el motivo viene en data.errorCode (código de Meta, ej. 131049, 131026) y data.errorMessage.
Los eventos pueden llegar fuera de orden (raro, pero posible). No asumas la secuencia sent → delivered → read: por cada clientRef/id quedate con el estado más avanzado (read > delivered > sent) y tratá failed como terminal.
conversation.created
Llega cuando se crea una conversación nueva (primer mensaje de un contacto).
{
"event": "conversation.created",
"webhookId": "...",
"deliveredAt": "2026-04-19T12:34:56.800Z",
"attempt": 1,
"data": {
"id": "uuid-conversacion",
"contactId": "uuid-contacto",
"phoneNumberId": "uuid-canal",
"status": "open",
"assignedTo": null,
"lastMessageAt": "2026-04-19T12:34:55.000Z",
"lastMessagePreview": "Hola, tienen stock?",
"lastMessageDirection": "inbound",
"lastMessageStatus": "delivered",
"serviceWindowExpiresAt": "2026-04-20T12:34:55.000Z",
"createdAt": "2026-04-19T12:34:55.000Z",
"contact": {
"id": "uuid-contacto",
"name": "María Pérez",
"phoneNumber": "+5491126032641"
}
}
}
Llega cuando se crea un contacto nuevo (vía API o porque escribió por primera vez).
{
"event": "contact.created",
"webhookId": "...",
"deliveredAt": "2026-04-19T12:34:56.800Z",
"attempt": 1,
"data": {
"id": "uuid-contacto",
"phoneNumber": "+5491126032641",
"name": "María Pérez",
"email": null,
"tags": [],
"customFields": {},
"optedIn": true,
"lastMessageAt": "2026-04-19T12:34:55.000Z",
"createdAt": "2026-04-19T12:34:55.000Z"
}
}
Idempotencia y orden
- Un mismo evento puede entregarse más de una vez (por ejemplo si tu URL responde 200 pero la conexión se corta antes de que Waspy lo registre). Deduplicá por
data.id + event.
- Para
message.received y conversation.created, contact.created se entrega primero (cuando aplica).
- No garantizamos orden estricto entre suscripciones distintas.