> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waspytech.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Catálogo de eventos

> Payloads exactos de cada evento que entrega Waspy.

Todo webhook entregado por Waspy comparte el mismo envelope:

```json theme={null}
{
  "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).

```json theme={null}
{
  "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](/api-reference/media).

## `message.status_changed`

Llega cuando cambia el estado de un mensaje **saliente** que vos enviaste.

```json theme={null}
{
  "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`.

<Warning>
  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.
</Warning>

## `conversation.created`

Llega cuando se crea una conversación nueva (primer mensaje de un contacto).

```json theme={null}
{
  "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"
    }
  }
}
```

## `contact.created`

Llega cuando se crea un contacto nuevo (vía API o porque escribió por primera vez).

```json theme={null}
{
  "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.
