Payloads
Esta página documenta los payloads de los eventos normales de webhooks:
conversation.*message.*client.*referral.received(anuncios de Meta)
WhatsApp Flows (whatsapp.flows.*) usa el mismo envelope, con resourceType: "flow_response", pero su data y la respuesta esperada están documentadas en 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 \| flow_response | 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. Para conversaciones, mensajes y clientes, 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.
Para WhatsApp Flows, source es siempre whatsapp.flows.
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 para eventos de conversación/mensaje, no en source.
Salvo whatsapp.flows, el source no 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 |
whatsapp.flows.* | whatsapp.flows |
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": []
}
}
} Anuncios de Meta
Evento:
| Evento | changes | resourceType |
|---|---|---|
referral.received | null | client |
Se emite cuando llega un mensaje entrante atribuido a un anuncio de Meta (Click-to-WhatsApp, Click-to-Messenger o anuncios de Instagram). A diferencia de los eventos client.*, su data no es el cliente normalizado: contiene client, conversation y un objeto referral con la atribución estructurada del anuncio.
{
"event": "referral.received",
"resourceType": "client",
"resourceId": "client_sample_abc123",
"changes": null,
"data": {
"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",
"platform": "whatsapp",
"channelId": "wb-123456789012345"
},
"referral": {
"platform": "whatsapp",
"source": "ad",
"ad_id": "120211234567890123",
"ctwa_clid": "ARAkLkA8rmlFeiCktEJQ-QTwRiyYHAFDLMNDBH0CD3qpjd0HR4irJ6LEkR7JwLzMDopn7vghDWqTXUYWmTzID29SrqLOgUSDRAsNH0_sample",
"ref": null,
"headline": "Anuncio de prueba",
"body": "Texto principal del anuncio de prueba.",
"media_type": "image",
"image_url": "https://example.com/ad-image.jpg",
"video_url": null,
"thumbnail_url": null,
"welcome_message": "Hola, quiero más información",
"user_text": "Hola, quiero más información",
"channelId": "wb-123456789012345",
"messageId": "wamid.sample",
"conversationId": "521234567890",
"receivedAt": "2026-05-06T19:00:00.000Z"
}
}
} | Campo | Tipo | Descripción |
|---|---|---|
platform | string | Plataforma del canal: whatsapp, messenger, instagram |
source | string \| null | Tipo de origen reportado por Meta, normalmente ad |
ad_id | string \| null | Identificador del anuncio de Meta |
ctwa_clid | string \| null | Click ID de Click-to-WhatsApp, útil para la Conversions API |
ref | string \| null | Parámetro ref del anuncio (Click-to-Messenger / m.me) cuando aplica |
headline | string \| null | Título del anuncio |
body | string \| null | Texto principal del anuncio |
media_type | string \| null | Tipo de medio del anuncio: image, video, etc. |
image_url | string \| null | URL de la imagen del anuncio |
video_url | string \| null | URL del video del anuncio |
thumbnail_url | string \| null | URL de la miniatura del anuncio |
welcome_message | string \| null | Mensaje de bienvenida prellenado en el anuncio |
user_text | string \| null | Texto que envió el cliente al iniciar la conversación |
channelId | string \| null | Canal donde llegó el mensaje |
messageId | string \| null | Identificador del mensaje entrante que disparó el evento |
conversationId | string \| null | Identificador de la conversación |
receivedAt | string \| null | Fecha ISO-8601 en que se recibió el mensaje atribuido |
referral.received solo se emite en canales conectados directo con Meta (Click-to-WhatsApp, Click-to-Messenger y anuncios de Instagram). Los campos del anuncio que Meta no envíe llegan como null.