Payloads
Esta página documenta los payloads de los eventos normales de webhooks:
conversation.*message.*comment.*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 \| comment \| 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, comentarios 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,comment,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 se lee en data.conversation.platform para eventos de conversación y mensaje, o en data.comment.platform para comentarios; no se deriva de source.
Salvo whatsapp.flows, el source no depende del proveedor ni de detalles de implementación. Se mantiene estable entre cambios de implementación.
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, api.client.blocked, api.client.unblocked, api.client.strikes_reset, api.conversation.status_changed, dashboard.client.updated, dashboard.client.blocked, dashboard.client.unblocked, dashboard.client.strikes_reset, 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 |
comment.created | inbound.comment.received |
comment.updated | inbound.comment.updated, dashboard.comment.updated |
comment.deleted | inbound.comment.deleted, dashboard.comment.deleted |
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 | ID único y canónico de la conversación |
conversationId | string \| null | ID del hilo de chat; no identifica al cliente |
canSendDirectMessage | boolean | Solo puede ser false en WhatsApp, Instagram y Messenger/Facebook; se calcula desde el último mensaje del usuario. En chats internos siempre es true |
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 |
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": {}
}
} Comentarios
Eventos:
| Evento | changes |
|---|---|
comment.created | null |
comment.updated | Cambios por campo con { "before", "after" }: content, contentType, attachments, isSpam, moderation |
comment.deleted | null |
En eventos comment.*, resourceType es comment y data siempre contiene client, post, thread y comment. client es un resumen corto cuando el autor está asociado a un cliente, o null en caso contrario. La plataforma se encuentra en data.comment.platform.
Ejemplo de comment.created:
{
"id": "evt_comment_demo_01",
"event": "comment.created",
"workspaceId": "ws_demo_456",
"timestamp": "2026-08-31T15:42:18.000Z",
"source": "inbound.comment.received",
"resourceType": "comment",
"resourceId": "comment_demo_01",
"changes": null,
"data": {
"client": {
"id": "client_demo_01",
"phoneNumber": null,
"creationDate": "2026-04-12T17:05:00.000Z",
"lastUpdate": "2026-08-31T15:42:18.000Z",
"name": "Ana López",
"firstname": "Ana",
"email": null
},
"post": {
"id": "post_demo_01",
"externalId": "18000000000123456",
"platform": "instagram",
"channelId": "channel_demo_01",
"title": "¿Cuál color prefieres para este verano?",
"permalink": "https://www.instagram.com/p/DemoPost123/",
"contactName": "Platica Demo"
},
"thread": {
"id": "comment_demo_01",
"status": "open",
"needsReply": true,
"commentCount": 1,
"topic": "Comentarios de Instagram",
"tags": []
},
"comment": {
"id": "comment_demo_01",
"postId": "18000000000123456",
"parentCommentId": null,
"rootCommentId": "comment_demo_01",
"permalink": "https://www.instagram.com/p/DemoPost123/c/DemoComment456/",
"platform": "instagram",
"channelId": "channel_demo_01",
"content": "Me encanta el azul 💙",
"contentType": "text",
"attachments": [],
"creationDate": "2026-08-31T15:42:18.000Z",
"lastUpdate": "2026-08-31T15:42:18.000Z",
"direction": "incoming",
"role": "user",
"clientId": "client_demo_01",
"authorName": "Ana López",
"authorPhotoUrl": "https://cdn.example.com/profiles/ana-demo.jpg",
"authorType": null,
"isSpam": false,
"moderation": {
"action": "none",
"synced": false,
"moderatedAt": null
}
}
}
} post resume la publicación relacionada:
| Campo | Tipo | Descripción |
|---|---|---|
id | string \| null | Identificador de la publicación |
externalId | string \| null | Identificador del contenido en la plataforma |
platform | string \| null | instagram, facebook u otra plataforma compatible |
channelId | string \| null | Canal conectado |
title | string \| null | Texto o título de la publicación |
permalink | string \| null | URL pública de la publicación |
contactName | string \| null | Nombre mostrado para la cuenta del canal |
thread resume el hilo del comentario:
| Campo | Tipo | Descripción |
|---|---|---|
id | string \| null | Identificador del hilo; coincide con comment.rootCommentId |
status | string \| null | Estado actual del hilo |
needsReply | boolean | Indica si el hilo necesita respuesta |
commentCount | number | Número de comentarios del hilo |
topic | string \| null | Tema del hilo |
tags | string[] | Etiquetas del hilo |
comment contiene el comentario afectado:
| Campo | Tipo | Descripción |
|---|---|---|
id | string \| null | Identificador del comentario |
postId | string \| null | Identificador externo de la publicación; coincide con post.externalId |
parentCommentId | string \| null | Comentario padre, si es una respuesta |
rootCommentId | string \| null | Comentario raíz del hilo |
permalink | string \| null | URL pública del comentario |
platform | string \| null | Plataforma del comentario |
channelId | string \| null | Canal conectado |
content | string | Texto del comentario |
contentType | string | Tipo de contenido, normalmente text |
attachments | object[] | Adjuntos públicos del comentario |
creationDate | string \| null | Fecha ISO-8601 de creación |
lastUpdate | string \| null | Fecha ISO-8601 de última actualización |
direction | incoming \| outgoing \| null | incoming para clientes y outgoing para la marca |
role | string \| null | Rol normalizado del autor |
clientId | string \| null | Cliente asociado, cuando existe |
authorName | string \| null | Nombre mostrado del autor |
authorPhotoUrl | string \| null | URL de la foto pública del autor |
authorType | string \| null | Tipo de autor |
isSpam | boolean | Indica si el comentario está marcado como spam |
moderation | { "action": "none" \| "hidden" \| "deleted", "synced": boolean, "moderatedAt": string \| null } | Estado de moderación |
Cada elemento de attachments puede incluir:
| Campo | Tipo | Descripción |
|---|---|---|
id | string \| null | Identificador del adjunto |
type | string | image, video, link o unsupported |
status | string | Estado de disponibilidad del adjunto |
url | string \| null | URL disponible para el integrador |
mime | string \| null | Tipo MIME, cuando está disponible |
title | string \| null | Título del adjunto |
bytes | number \| null | Tamaño en bytes, cuando está disponible |
En comment.updated, cada campo modificado incluye su valor anterior y posterior. data conserva la misma forma y contiene el snapshot posterior al cambio. Por ejemplo, el fragmento changes de una moderación puede ser:
{
"moderation": {
"before": {
"action": "none",
"synced": false,
"moderatedAt": null
},
"after": {
"action": "hidden",
"synced": true,
"moderatedAt": "2026-08-31T15:48:02.000Z"
}
}
} 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, status, moderation, creationDate, lastUpdate, deleted |
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,
"status": "active",
"moderation": {
"totalStrikes": 0,
"lastAction": null,
"lastSource": null,
"lastSeverity": null,
"lastFlaggedAt": null,
"lastCategories": [],
"lastMatchedTerms": [],
"blockedAt": null,
"blockedBy": null,
"blockedReason": null
},
"tags": ["vip"],
"owners": ["agente1@empresa.com"],
"customFields": {
"placas": "ABC123"
},
"creationDate": "2026-05-06T19:00:00.000Z",
"lastUpdate": "2026-05-06T19:00:00.000Z",
"deleted": false
} 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.
Bloqueo y moderación
status y moderation viajan siempre en data, y moderation viene completo aunque el cliente no tenga incidentes. Los campos de moderation están documentados en Obtener un Cliente .
Cuando bloqueas o desbloqueas a un cliente recibes un client.updated con changes.status, y además un conversation.status.updated por cada conversación abierta que se sincronizó. Si te interesa reaccionar sólo al bloqueo en sí, filtra por el source del evento y no por la cantidad de eventos recibidos.
| Acción | source del client.updated |
|---|---|
| Bloqueo desde la API | api.client.blocked |
| Desbloqueo desde la API | api.client.unblocked |
| Reset de strikes desde la API | api.client.strikes_reset |
| Bloqueo / desbloqueo desde el panel | dashboard.client.blocked, dashboard.client.unblocked |
| Reset de strikes desde el panel | dashboard.client.strikes_reset |
| Bloqueo automático por moderación | system.moderation.applied |
| Conversación marcada como spam | api.conversation.status_changed |
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.