Payloads
Esta página documenta los payloads de los eventos normales de webhooks:
conversation.*message.*client.*
Para WhatsApp Flows (whatsapp.flows.*), consulta WhatsApp Flows , porque esos eventos requieren que tu endpoint responda con datos para continuar el flow.
Envelope común
Todos estos eventos llegan a tu endpoint con un POST JSON usando esta estructura:
{
"id": "9f8c...",
"event": "conversation.created",
"workspaceId": "ws_456",
"timestamp": "2026-05-06T19:00:00.000Z",
"source": "system.conversation.created",
"resourceType": "conversation",
"resourceId": "conv_123",
"changes": null,
"data": {}
} | Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador único del evento |
event | string | Nombre del evento que disparó el webhook |
workspaceId | string | Workspace donde ocurrió el evento |
timestamp | string | Fecha ISO-8601 del evento |
source | string | Origen del evento — ver Campo source |
resourceType | conversation \| message \| client | Tipo de recurso afectado |
resourceId | string \| null | Identificador del recurso afectado |
changes | object \| null | Cambios detectados. Es null en eventos de creación / eliminación |
data | object | Snapshot normalizado del recurso |
Si sólo quieres validar que el webhook sea auténtico, ve a Entrega y firmas .
Campo source
Identifica el origen del evento. Su valor sigue el formato:
<origin>.<resource>.<action>[.<qualifier>] - origin: uno de
api,dashboard,inbound,agent,system,scheduler,campaign. - resource:
client,conversation,message,audience,scheduled_event,webhook,moderation,supervisor. - action: verbo / estado (
created,updated,deleted,processed,dispatched, etc.). - qualifier (opcional):
bulk,auto,campaign.
Usa el source sólo para auditoría / debugging. La identidad lógica del evento (qué cambió y qué recursos afecta) está en event, resourceType, resourceId y changes. La plataforma del canal (whatsapp, instagram, …) se lee en data.conversation.platform, no en source.
El source nunca incluye nombres de proveedor (messagebird, meta, whatsapp) ni rutas internas del código. Es estable: si refactorizamos el archivo emisor, el source no cambia.
Ejemplos comunes por evento:
event | Posibles valores de source |
|---|---|
client.created | api.client.created, inbound.client.created, campaign.client.created |
client.updated | api.client.updated, dashboard.client.updated, inbound.client.profile_updated, inbound.client.refreshed, inbound.client.identifiers_updated, agent.supervisor.applied, system.moderation.applied |
client.tags.updated | api.client.tagged.bulk, api.audience.added, api.audience.removed, api.audience.cleared, dashboard.client.updated |
client.customFields.updated | api.client.custom_fields_updated, inbound.client.custom_fields_updated, agent.client.confirmation_saved, dashboard.client.updated |
conversation.created | system.conversation.created, dashboard.conversation.created, campaign.conversation.created |
conversation.status.updated | api.conversation.updated, api.conversation.finished, api.conversation.marked_spam, api.conversation.archived, api.conversation.reactivated, dashboard.conversation.stopped, campaign.conversation.updated |
conversation.expired | system.conversation.expired_by_inactivity, agent.conversation.expired, api.conversation.archived.auto, api.conversation.updated |
conversation.owners.updated | api.conversation.operator_assigned, api.conversation.operators_set, dashboard.conversation.assistance_accepted |
conversation.tags.updated | api.conversation.tagged, dashboard.conversation.assistance_requested |
message.created | inbound.message.received, agent.message.generated, agent.message.processed, agent.message.dispatched, agent.message.service_sent, agent.message.summary_generated, campaign.message.received, dashboard.message.system_inserted, scheduler.scheduled_event.created |
message.updated | inbound.message.delivery_updated, inbound.message.delivery_failed, inbound.message.delivery_updated.campaign, agent.message.processed, agent.message.status_updated, agent.message.dispatched, dashboard.message.resent, scheduler.scheduled_event.processing, scheduler.scheduled_event.sent, scheduler.scheduled_event.failed, api.scheduled_event.cancelled |
Conversaciones
Eventos:
| Evento | changes |
|---|---|
conversation.created | null |
conversation.status.updated | { "status": { "before", "after" } } |
conversation.operation.updated | { "operation": { "before", "after" } } |
conversation.owners.updated | { "owners": { "added": [], "removed": [] } } |
conversation.tags.updated | { "tags": { "added": [], "removed": [] } } |
conversation.expired | { "isFinished": { "before", "after": true } } |
En eventos conversation.*, data siempre tiene:
{
"client": {
"id": "521234567890",
"phoneNumber": "521234567890",
"creationDate": "2026-05-06T19:00:00.000Z",
"lastUpdate": "2026-05-06T19:00:00.000Z",
"name": "Juan Pérez",
"firstname": "Juan",
"email": null
},
"conversation": {
"id": "conv_123",
"conversationId": "521234567890",
"canSendDirectMessage": true,
"workspaceId": "ws_456",
"channelId": "521555000111",
"contactName": "Juan Pérez",
"phoneNumber": "521234567890",
"topic": "",
"platform": "whatsapp",
"owners": [],
"tags": [],
"creationDate": "2026-05-06T19:00:00.000Z",
"lastUpdate": "2026-05-06T19:00:00.000Z",
"status": "open",
"operation": "automatic",
"messageCount": 1,
"messages": []
}
} | Campo | Tipo | Descripción |
|---|---|---|
id | string \| null | Identificador interno |
conversationId | string \| null | Identificador externo, normalmente el teléfono |
canSendDirectMessage | boolean | false si la conversación está cerrada, expirada, spam o fuera de la ventana de 24 h en WhatsApp |
workspaceId | string \| null | Workspace propietario |
channelId | string \| null | Canal donde ocurrió |
contactName | string \| null | Nombre del contacto |
phoneNumber | string \| null | Teléfono del contacto |
topic | string | Tema de la conversación |
platform | string \| null | Canal del contacto: whatsapp, instagram, messenger, sms, etc. |
owners | string[] | Propietarios asignados, identificados por email (no UID) |
tags | string[] | Etiquetas asignadas |
creationDate | string \| null | Fecha ISO-8601 |
lastUpdate | string \| null | Fecha ISO-8601 |
status | string \| null | Estado actual: open, pending, finished, blocked, spam, expired |
operation | string \| null | Modo de la conversación: automatic (atendida por bot) o manual (atendida por operador) |
messageCount | number | Número de mensajes |
messages | unknown[] | Mensajes serializados. Sólo aparece en eventos conversation.* |
Mensajes
Eventos:
| Evento | changes |
|---|---|
message.created | null |
message.updated | Cambios por campo: content, contentBlocks, contentType, direction, lastUpdate, role, status, files, images, owner |
message.deleted | null — declarado en el catálogo, pero no se emite todavía. Suscribirse no produce eventos hoy. |
En eventos message.*, data tiene client, conversation y message. La conversación no incluye messages[]; el mensaje afectado va en data.message.
{
"client": { "...": "WebhookClientSummary" },
"conversation": { "...": "WebhookConversation sin messages[]" },
"message": {
"id": "msg_789",
"content": "Hola, necesito ayuda con mi pedido",
"contentBlocks": [
{ "type": "text", "text": "Hola, necesito ayuda con mi pedido" }
],
"contentType": "text",
"creationDate": "2026-05-06T19:00:00.000Z",
"direction": "incoming",
"files": [],
"images": [],
"owner": null,
"lastUpdate": "2026-05-06T19:00:00.000Z",
"role": "user",
"status": "delivered"
}
} | Campo | Tipo | Descripción |
|---|---|---|
id | string \| null | Identificador del mensaje |
content | string | Texto plano derivado del mensaje |
contentBlocks | unknown[] | Bloques estructurados |
contentType | string | text por defecto |
creationDate | string \| null | Fecha ISO-8601 |
direction | incoming \| outgoing \| string \| null | Siempre se normaliza a incoming u outgoing. Valores como received o sent se traducen antes de emitirse |
files | unknown[] | Archivos adjuntos |
images | unknown[] | Imágenes adjuntas |
owner | { "id": string } \| null | Autor del mensaje. id es el email del operador cuando aplica |
lastUpdate | string \| null | Fecha ISO-8601 |
role | string \| null | user, assistant, tool, etc. |
status | string \| null | Estado del mensaje. Valores comunes: received, sent, delivered, read, failed |
El message no tiene text, timestamp, senderId, senderName, conversationId ni type. Esos campos no existen en el envelope — usa los del schema anterior.
Ejemplo de message.updated:
{
"event": "message.updated",
"resourceType": "message",
"resourceId": "msg_789",
"changes": {
"status": {
"before": "sent",
"after": "read"
}
},
"data": {
"client": {},
"conversation": {},
"message": {}
}
} Clientes
Eventos:
| Evento | changes |
|---|---|
client.created | null |
client.updated | Cambios por campo básico: phoneNumber, name, firstname, lastname, email, birthdate, gender, company, country, state, city, address, postalCode, creationDate, lastUpdate |
client.owners.updated | { "owners": { "added": [], "removed": [] } } |
client.tags.updated | { "tags": { "added": [], "removed": [] } } |
client.customFields.updated | { "customFields": { "<campo>": { "before", "after" } } } o { "customFields": { "<campo>": { "added": [], "removed": [] } } } |
En eventos client.*, data es directamente el cliente normalizado:
{
"id": "521234567890",
"workspaceId": "ws_456",
"phoneNumber": "521234567890",
"name": "Juan Pérez",
"firstname": "Juan",
"lastname": "Pérez",
"email": "juan@empresa.com",
"birthdate": null,
"gender": null,
"company": "Acme Inc.",
"country": "MX",
"state": null,
"city": null,
"address": null,
"postalCode": null,
"tags": ["vip"],
"owners": ["agente1@empresa.com"],
"customFields": {
"placas": "ABC123"
},
"creationDate": "2026-05-06T19:00:00.000Z",
"lastUpdate": "2026-05-06T19:00:00.000Z"
} En customFields, los campos tipo lista se truncan a los últimos 5 ítems dentro de data. En client.customFields.updated, el objeto changes se calcula con los valores completos, sin truncar.
Ejemplo de client.customFields.updated:
{
"event": "client.customFields.updated",
"resourceType": "client",
"resourceId": "521234567890",
"changes": {
"customFields": {
"placas": {
"before": "ABC123",
"after": "XYZ789"
},
"visitas": {
"added": [
{
"id": "visit_5",
"creationDate": "2026-05-06T19:00:00.000Z",
"lastUpdate": "2026-05-06T19:00:00.000Z",
"content": "Visita de seguimiento"
}
],
"removed": []
}
}
},
"data": {
"id": "521234567890",
"customFields": {
"placas": "XYZ789",
"visitas": []
}
}
}