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

# Catálogo de eventos

> Cada tipo de evento de webhook que envía Ciarem, cuándo se dispara y el cuerpo exacto que lleva.

## El sobre

Todos los eventos tienen la misma forma exterior. Solo `data` cambia de un tipo a otro.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.created",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": { }
}
```

| Campo | Tipo | |
| - | - | - |
| `id` | string | Id único del evento. Se mantiene igual entre reintentos y cuando envías una entrega de nuevo, así que es el valor para deduplicar. |
| `type` | string | El tipo de evento, uno de la lista de abajo. |
| `api_version` | string | Hoy siempre es `v1`. |
| `created_at` | string | Cuándo ocurrió el evento en Ciarem. ISO 8601, UTC, terminado en `Z`. |
| `data` | object | Los detalles del evento. |

## Todos los tipos de evento

| `type` | Se dispara cuando | `data` lleva |
| - | - | - |
| `contact.created` | Se agrega un contacto, por cualquier medio | El contacto completo |
| `contact.updated` | Cambian los campos o las propiedades personalizadas de un contacto | El contacto completo y qué cambió |
| `contact.deleted` | Se elimina un contacto | El id del contacto |
| `contact.merged` | Dos contactos se fusionan en uno | El contacto que sobrevive y el id que se fusionó en él |
| `contact.funnel_stage.changed` | Un contacto pasa a otra etapa del embudo | El contacto completo y la etapa anterior |
| `contact.owner.changed` | Cambia el propietario de un contacto | El contacto completo y el propietario anterior |
| `conversation.created` | Empieza una conversación nueva | Los ids de la conversación, el contacto y el canal |
| `conversation.assigned` | Una conversación se asigna a un compañero | Los mismos ids, más a quién se asignó |
| `conversation.closed` | Una conversación se resuelve | Los ids de la conversación, el contacto y el canal |
| `messages.inbound` | Un cliente envía un mensaje | Los ids de la conversación, el contacto y el canal |
| `whatsapp_flow.completed` | Un cliente envía un [formulario de WhatsApp](https://help.ciarem.ai/es/channels/whatsapp-forms.md) | El formulario, el contacto, las respuestas y lo que Ciarem guardó |

Los eventos de contacto llevan el **contacto completo**, así que la mayoría de las integraciones nunca necesitan volver a llamar a la API. Los eventos de conversación y de mensaje llevan **solo ids**: lee la conversación o sus mensajes desde la [API](https://help.ciarem.ai/es/api-reference/overview) cuando necesites el contenido.

> [!NOTE]
>
> Con el tiempo se agregan nuevos tipos de evento. Un endpoint suscrito a **todos los eventos** empieza a recibirlos automáticamente. Responde `200` a cualquier `type` que no manejes.

## El objeto contacto

Los eventos de contacto incluyen un objeto `contact` con los mismos campos que `GET /v1/contacts/{id}`:

```json theme={null}
{
  "id": "cnt_…",
  "first_name": "Ana",
  "last_name": "García",
  "email": "ana@example.com",
  "phone": "+5215550100",
  "country": "MX",
  "city": "Ciudad de México",
  "instagram": null,
  "messenger": null,
  "whatsapp_username": null,
  "origin": "MANUAL",
  "sub_origin_id": null,
  "whatsapp_status": "verified",
  "marketing_opt_out": false,
  "funnel_stage_id": "stg_…",
  "owner_id": null,
  "properties": {
    "custom_budget": {
      "property_id": "prp_…",
      "kind": "NUMBER",
      "value_text": null,
      "value_number": 5000,
      "value_bool": null,
      "value_date": null,
      "value_json": null
    }
  },
  "last_contacted_at": null,
  "created_at": "2026-01-15T14:30:00Z",
  "updated_at": "2026-01-15T14:30:00Z"
}
```

Tres cosas que debes saber:

* **`properties`** contiene tus propiedades personalizadas, indexadas por la clave de la propiedad. Lee `kind` para saber qué campo `value_*` contiene el valor.
* **Las propiedades de archivo se omiten.** El enlace de descarga de un archivo es temporal y expiraría antes de que llegue una entrega reintentada. Lee el contacto desde la API para obtener un enlace nuevo.
* **El contacto es una foto tomada cuando Ciarem prepara el evento**, un momento después del cambio. Si el contacto cambió dos veces seguidas, ambos eventos pueden mostrar el estado más reciente. Un reintento envía la misma foto otra vez, no una nueva.

## Eventos de contacto

### contact.created

Se agregó un contacto: lo hizo un compañero, una importación, la API, una integración como HubSpot, o una persona nueva escribió por un canal.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.created",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "contact": { "id": "cnt_…", "first_name": "Ana", "…": "…" }
  }
}
```

### contact.updated

Cambiaron los campos propios de un contacto (nombre, correo, teléfono, ciudad, etc.) o una de sus propiedades personalizadas.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.updated",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "contact": { "id": "cnt_…", "first_name": "Ana María", "…": "…" },
    "changed_fields": ["first_name"]
  }
}
```

`changed_fields` lista qué cambió:

* Un campo simple aparece por su nombre: `"first_name"`, `"email"`.
* Una propiedad personalizada aparece como `properties.<key>`: `"properties.custom_budget"`. Cada propiedad personalizada que cambia envía **su propio** evento `contact.updated`.

Los cambios de etapa del embudo y de propietario **no** se reportan aquí. Tienen sus propios eventos, más abajo. Una edición que cambia a la vez un nombre, la etapa y el propietario envía tres eventos: `contact.updated`, `contact.funnel_stage.changed` y `contact.owner.changed`.

### contact.deleted

Se eliminó un contacto. El contacto ya no existe, así que solo se envía su id.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.deleted",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "contact_id": "cnt_…"
  }
}
```

### contact.merged

Se fusionaron dos contactos. `contact` es el que sobrevive, con el resultado de la fusión. `merged_contact_id` es el contacto que se integró en él y ya no existe por separado: actualiza los registros de tu lado que todavía apunten a él.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.merged",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "contact": { "id": "cnt_…", "…": "…" },
    "merged_contact_id": "cnt_…"
  }
}
```

### contact.funnel\_stage.changed

Un contacto pasó a otra etapa del embudo, ya sea que lo movió un compañero o el agente de IA. La etapa nueva es `contact.funnel_stage_id`; la que dejó es `previous_funnel_stage_id`. Convierte los ids de etapa en nombres con `GET /v1/funnel-stages`.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.funnel_stage.changed",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "contact": { "id": "cnt_…", "funnel_stage_id": "stg_…", "…": "…" },
    "previous_funnel_stage_id": "stg_…"
  }
}
```

### contact.owner.changed

Cambió el propietario de un contacto. El propietario nuevo es `contact.owner_id`; el anterior es `previous_owner_id`. Cualquiera de los dos puede ser `null`, lo que significa que el contacto no tiene, o no tenía, propietario.

```json theme={null}
{
  "id": "evt_…",
  "type": "contact.owner.changed",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "contact": { "id": "cnt_…", "owner_id": "mbr_…", "…": "…" },
    "previous_owner_id": null
  }
}
```

## Eventos de conversación y de mensaje

Estos cuatro eventos comparten el mismo `data` base:

| Campo | Tipo | |
| - | - | - |
| `conversation_id` | string | La conversación. Léela con `GET /v1/conversations/{id}`. |
| `contact_id` | string o null | El contacto del otro lado. |
| `channel_kind` | string | `whatsapp`, `instagram`, `messenger` o `webchat`. |
| `channel_id` | string | El canal conectado (por ejemplo, qué número de WhatsApp). |

### conversation.created

Empezó una conversación nueva.

```json theme={null}
{
  "id": "evt_…",
  "type": "conversation.created",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "conversation_id": "conv_…",
    "contact_id": "cnt_…",
    "channel_kind": "whatsapp",
    "channel_id": "chn_…"
  }
}
```

> [!NOTE]
>
> Ciarem espera unos segundos a que el contacto quede vinculado antes de enviar este evento, así que `contact_id` casi siempre está presente. En el caso poco común de que siga en `null`, lee la conversación desde la API un poco después. Esta breve espera también significa que el primer `messages.inbound` de una conversación puede llegar **antes** que su `conversation.created`.

### conversation.assigned

Se asignó una conversación: la tomó un compañero, la asignó un administrador, lo hicieron tus reglas de asignación automática, o el agente de IA la transfirió.

```json theme={null}
{
  "id": "evt_…",
  "type": "conversation.assigned",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "conversation_id": "conv_…",
    "contact_id": "cnt_…",
    "channel_kind": "whatsapp",
    "channel_id": "chn_…",
    "assignee_id": "mbr_…",
    "assignee": {
      "id": "mbr_…",
      "name": "Juan Lopez",
      "email": "juan@example.com"
    },
    "team_id": null,
    "strategy": "round_robin",
    "assigned_by": "system",
    "self_assigned": false,
    "url": "https://www.ciarem.ai/dashboard/inbox?c=conv_…"
  }
}
```

`assignee_id` es el compañero y siempre viene. `team_id` es el equipo de la conversación y puede ser `null`.

| Campo | Tipo | |
| - | - | - |
| `assignee` | objeto o null | El `id`, `name` y `email` del compañero, para que una herramienta sin código (un workflow de HubSpot, Zapier, Make) muestre quién recibió el chat sin llamar a la API. `name` y `email` pueden ser `null`. |
| `strategy` | string | Cómo se asignó: `manual` (una persona eligió, o la IA se la entregó al compañero que indica una regla de transferencia), `round_robin` o `random` (tus reglas de asignación), `owner` (fue al propietario del contacto), `reassigned` (el asesor anterior no respondió a tiempo) o `solo_admin` (no había ningún asesor disponible, así que fue al único miembro de la organización, que es admin). |
| `assigned_by` | string | `system` para las reglas y el agente de IA; si no, el id `mbr_…` del compañero que la asignó. |
| `self_assigned` | boolean | `true` cuando el compañero tomó la conversación por su cuenta, por ejemplo al responderla. Fíltralas si solo quieres enterarte de los chats que se le entregan a alguien. |
| `url` | string | Abre la conversación en el inbox de Ciarem. |

### conversation.closed

Se resolvió una conversación, ya sea porque un compañero terminó el chat o porque el agente de IA la cerró. El cuerpo es el `data` base de arriba.

```json theme={null}
{
  "id": "evt_…",
  "type": "conversation.closed",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "conversation_id": "conv_…",
    "contact_id": "cnt_…",
    "channel_kind": "whatsapp",
    "channel_id": "chn_…"
  }
}
```

### messages.inbound

Un cliente envió un mensaje. El evento te dice **qué conversación** tiene algo nuevo; no contiene el mensaje en sí.

```json theme={null}
{
  "id": "evt_…",
  "type": "messages.inbound",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {
    "conversation_id": "conv_…",
    "contact_id": "cnt_…",
    "channel_kind": "whatsapp",
    "channel_id": "chn_…"
  }
}
```

Para leer lo que se dijo, trae los mensajes de la conversación:

```bash theme={null}
curl https://api.ciarem.ai/v1/conversations/conv_…/messages \
  -H "Authorization: Bearer oak_TU_SECRETO"
```

Los mensajes que **envían** tu equipo o el agente de IA no disparan un evento.

## Eventos de formularios de WhatsApp

### whatsapp\_flow\.completed

Un cliente envió un [formulario de WhatsApp](https://help.ciarem.ai/es/channels/whatsapp-forms.md). Se dispara una vez por envío, después de que Ciarem guardó las respuestas.

```json theme={null}
{
  "id": "evt_…",
  "type": "whatsapp_flow.completed",
  "api_version": "v1",
  "created_at": "2026-10-15T15:04:05.123456Z",
  "data": {
    "session_id": "wafs_…",
    "flow_id": "waflow_…",
    "contact_id": "cnt_…",
    "conversation_id": "conv_…",
    "answers": { "q1": "Ana", "q2": "ana@example.com" },
    "outcome": { "saved": ["contact:first_name", "contact:email"], "errors": [] }
  }
}
```

* `answers` usa los nombres internos de los campos del formulario, no el texto de la pregunta. Para leer los valores por propiedad, usa `outcome.saved` (los campos y propiedades del contacto donde se escribieron las respuestas) y lee el contacto.
* `outcome.errors` lista las respuestas que no se pudieron guardar, por ejemplo un email que pertenece a otro contacto.
* Un formulario de reserva también trae `booking_id`, `start_at`, `end_at`, `meeting_url` y `attendee_email` en `outcome`.
* La respuesta de una pregunta de fotos o documentos es una lista de `{"file_name", "mime_type"}`, y `outcome.files` da su estado: `{"q3": {"status": "pending"}}`. El evento se envía antes de que se guarden los archivos. Cuando la pregunta se guarda en una propiedad de archivo, los archivos guardados llegan con el evento [`contact.updated`](#eventos-de-contacto) que sigue.
* `conversation_id` puede ser `null` cuando el formulario se envió con una plantilla fuera de una conversación.

## El evento de prueba

Una prueba enviada desde el panel o con `POST /v1/webhook-endpoints/{id}/test` se ve como un evento real, con dos diferencias:

* Su `id` empieza con `evt_test_`, así que puedes distinguirlo y saltarte cualquier efecto real.
* Si no eliges un tipo de evento, su `type` es `test` y su `data` está vacío:

```json theme={null}
{
  "id": "evt_test_…",
  "type": "test",
  "api_version": "v1",
  "created_at": "2026-10-09T15:04:05.123456Z",
  "data": {}
}
```

Cuando sí eliges un tipo, la prueba lleva datos de ejemplo con exactamente la forma que tiene un evento real. `GET /v1/webhook-event-samples` devuelve el cuerpo de ejemplo de cada tipo, lo que resulta útil para escribir pruebas de tu propio handler.

## Compatibilidad

Los cuerpos de los webhooks siguen la misma regla que el resto de la API: los cambios son **aditivos**. En cualquier momento pueden aparecer nuevos tipos de evento, nuevos campos dentro de `data` y nuevos valores en campos de texto. Lee los campos que necesitas, ignora el resto, y revisa el [changelog](https://help.ciarem.ai/es/api-reference/changelog.md) cuando algo se vea distinto.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.