Payloads
This page documents the payloads of regular webhook events:
conversation.*message.*client.*referral.received(Meta ads)
WhatsApp Flows (whatsapp.flows.*) use the same envelope, with resourceType: "flow_response", but their data and expected response are documented in WhatsApp Flows , since those events require your endpoint to respond with data to keep the flow going.
Common envelope
All these events reach your endpoint via a JSON POST with this structure:
{
"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": {}
} | Field | Type | Description |
|---|---|---|
id | string | Unique event identifier |
event | string | Name of the event that triggered the webhook |
workspaceId | string | Workspace where the event happened |
timestamp | string | ISO-8601 date of the event |
source | string | Origin of the event — see the source field |
resourceType | conversation \| message \| client \| flow_response | Type of resource affected |
resourceId | string \| null | Identifier of the affected resource |
changes | object \| null | Detected changes. null for creation/deletion events |
data | object | Normalized snapshot of the resource |
If you only want to verify that the webhook is authentic, go to Delivery and signatures .
The source field
Identifies the origin of the event. For conversation, message, and customer events, its value follows the format:
<origin>.<resource>.<action>[.<qualifier>] - origin: one of
api,dashboard,inbound,agent,system,scheduler,campaign. - resource:
client,conversation,message,audience,scheduled_event,webhook,moderation,supervisor. - action: verb / state (
created,updated,deleted,processed,dispatched, etc.). - qualifier (optional):
bulk,auto,campaign.
For WhatsApp Flows, source is always whatsapp.flows.
Use source only for auditing / debugging. The logical identity of the event (what changed and which resources it affects) lives in event, resourceType, resourceId, and changes. The channel platform (whatsapp, instagram, …) is read from data.conversation.platform for conversation/message events, not from source.
Except for whatsapp.flows, source does not include provider names (messagebird, meta, whatsapp) or internal code paths. It's stable: if we refactor the emitter file, the source does not change.
Common examples by event:
event | Possible source values |
|---|---|
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 |
Conversations
Events:
| Event | 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 } } |
For conversation.* events, data always has:
{
"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": []
}
} | Field | Type | Description |
|---|---|---|
id | string \| null | Internal identifier |
conversationId | string \| null | External identifier, usually the phone number |
canSendDirectMessage | boolean | false if the conversation is closed, expired, spam, or outside the 24-hour window on WhatsApp |
workspaceId | string \| null | Owning workspace |
channelId | string \| null | Channel where it happened |
contactName | string \| null | Contact name |
phoneNumber | string \| null | Contact phone number |
topic | string | Conversation topic |
platform | string \| null | Contact channel: whatsapp, instagram, messenger, sms, etc. |
owners | string[] | Assigned owners, identified by email (not UID) |
tags | string[] | Assigned tags |
creationDate | string \| null | ISO-8601 date |
lastUpdate | string \| null | ISO-8601 date |
status | string \| null | Current state: open, pending, finished, blocked, spam, expired |
operation | string \| null | Conversation mode: automatic (bot-handled) or manual (operator-handled) |
messageCount | number | Number of messages |
messages | unknown[] | Serialized messages. Only present in conversation.* events |
Messages
Events:
| Event | changes |
|---|---|
message.created | null |
message.updated | Per-field changes: content, contentBlocks, contentType, direction, lastUpdate, role, status, files, images, owner |
message.deleted | null — declared in the catalog, but not emitted yet. Subscribing produces no events today. |
For message.* events, data contains client, conversation, and message. The conversation does not include messages[]; the affected message is in 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"
}
} | Field | Type | Description |
|---|---|---|
id | string \| null | Message identifier |
content | string | Plain-text version of the message |
contentBlocks | unknown[] | Structured blocks |
contentType | string | text by default |
creationDate | string \| null | ISO-8601 date |
direction | incoming \| outgoing \| string \| null | Always normalized to incoming or outgoing. Values like received or sent are translated before being emitted |
files | unknown[] | Attached files |
images | unknown[] | Attached images |
owner | { "id": string } \| null | Message author. id is the operator's email when applicable |
lastUpdate | string \| null | ISO-8601 date |
role | string \| null | user, assistant, tool, etc. |
status | string \| null | Message status. Common values: received, sent, delivered, read, failed |
The message does not have text, timestamp, senderId, senderName, conversationId, or type. Those fields do not exist in the envelope — use the schema above.
Example of message.updated:
{
"event": "message.updated",
"resourceType": "message",
"resourceId": "msg_789",
"changes": {
"status": {
"before": "sent",
"after": "read"
}
},
"data": {
"client": {},
"conversation": {},
"message": {}
}
} Customers
Events:
| Event | changes |
|---|---|
client.created | null |
client.updated | Per basic-field changes: 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": { "<field>": { "before", "after" } } } or { "customFields": { "<field>": { "added": [], "removed": [] } } } |
For client.* events, data is the normalized customer directly:
{
"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"
} In customFields, list-type fields are truncated to the last 5 items inside data. In client.customFields.updated, the changes object is computed from the full values, with no truncation.
Example of 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": []
}
}
} Meta Ads
Event:
| Event | changes | resourceType |
|---|---|---|
referral.received | null | client |
Emitted when an inbound message arrives attributed to a Meta ad (Click-to-WhatsApp, Click-to-Messenger, or Instagram ads). Unlike client.* events, its data is not the normalized customer: it contains client, conversation, and a referral object with the structured ad attribution.
{
"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"
}
}
} | Field | Type | Description |
|---|---|---|
platform | string | Channel platform: whatsapp, messenger, instagram |
source | string \| null | Source type reported by Meta, usually ad |
ad_id | string \| null | Meta ad identifier |
ctwa_clid | string \| null | Click-to-WhatsApp click ID, useful for the Conversions API |
ref | string \| null | Ad ref parameter (Click-to-Messenger / m.me) when present |
headline | string \| null | Ad headline |
body | string \| null | Ad primary text |
media_type | string \| null | Ad media type: image, video, etc. |
image_url | string \| null | Ad image URL |
video_url | string \| null | Ad video URL |
thumbnail_url | string \| null | Ad thumbnail URL |
welcome_message | string \| null | Welcome message prefilled in the ad |
user_text | string \| null | Text the customer sent when starting the conversation |
channelId | string \| null | Channel where the message arrived |
messageId | string \| null | Identifier of the inbound message that triggered the event |
conversationId | string \| null | Conversation identifier |
receivedAt | string \| null | ISO-8601 date when the attributed message was received |
referral.received is only emitted on channels connected directly with Meta (Click-to-WhatsApp, Click-to-Messenger, and Instagram ads). Ad fields that Meta does not send arrive as null.