Payloads

This page documents the payloads of regular webhook events:

  • conversation.*
  • message.*
  • comment.*
  • 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": {}
}
FieldTypeDescription
idstringUnique event identifier
eventstringName of the event that triggered the webhook
workspaceIdstringWorkspace where the event happened
timestampstringISO-8601 date of the event
sourcestringOrigin of the event — see the source field
resourceTypeconversation \| message \| comment \| client \| flow_responseType of resource affected
resourceIdstring \| nullIdentifier of the affected resource
changesobject \| nullDetected changes. null for creation/deletion events
dataobjectNormalized snapshot of the resource

The source field

Identifies the origin of the event. For conversation, message, comment, 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, comment, 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. Read the channel platform from data.conversation.platform for conversation and message events, or from data.comment.platform for comments; do not derive it from source.

Common examples by event:

eventPossible source values
client.createdapi.client.created, inbound.client.created, campaign.client.created
client.updatedapi.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.updatedapi.client.tagged.bulk, api.audience.added, api.audience.removed, api.audience.cleared, dashboard.client.updated
client.customFields.updatedapi.client.custom_fields_updated, inbound.client.custom_fields_updated, agent.client.confirmation_saved, dashboard.client.updated
conversation.createdsystem.conversation.created, dashboard.conversation.created, campaign.conversation.created
conversation.status.updatedapi.conversation.updated, api.conversation.finished, api.conversation.marked_spam, api.conversation.archived, api.conversation.reactivated, dashboard.conversation.stopped, campaign.conversation.updated
conversation.expiredsystem.conversation.expired_by_inactivity, agent.conversation.expired, api.conversation.archived.auto, api.conversation.updated
conversation.owners.updatedapi.conversation.operator_assigned, api.conversation.operators_set, dashboard.conversation.assistance_accepted
conversation.tags.updatedapi.conversation.tagged, dashboard.conversation.assistance_requested
message.createdinbound.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.updatedinbound.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.createdinbound.comment.received
comment.updatedinbound.comment.updated, dashboard.comment.updated
comment.deletedinbound.comment.deleted, dashboard.comment.deleted
whatsapp.flows.*whatsapp.flows

Conversations

Events:

Eventchanges
conversation.creatednull
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": []
  }
}
FieldTypeDescription
idstring \| nullUnique, canonical conversation ID
conversationIdstring \| nullChat thread ID; it does not identify the client
canSendDirectMessagebooleanCan only be false on WhatsApp, Instagram, and Messenger/Facebook; calculated from the latest user message. Always true for internal chats
workspaceIdstring \| nullOwning workspace
channelIdstring \| nullChannel where it happened
contactNamestring \| nullContact name
phoneNumberstring \| nullContact phone number
topicstringConversation topic
platformstring \| nullContact channel: whatsapp, instagram, messenger, sms, etc.
ownersstring[]Assigned owners, identified by email
tagsstring[]Assigned tags
creationDatestring \| nullISO-8601 date
lastUpdatestring \| nullISO-8601 date
statusstring \| nullCurrent state: open, pending, finished, blocked, spam, expired
operationstring \| nullConversation mode: automatic (bot-handled) or manual (operator-handled)
messageCountnumberNumber of messages
messagesunknown[]Serialized messages. Only present in conversation.* events

Messages

Events:

Eventchanges
message.creatednull
message.updatedPer-field changes: content, contentBlocks, contentType, direction, lastUpdate, role, status, files, images, owner
message.deletednull — 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 without 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"
  }
}
FieldTypeDescription
idstring \| nullMessage identifier
contentstringPlain-text version of the message
contentBlocksunknown[]Structured blocks
contentTypestringtext by default
creationDatestring \| nullISO-8601 date
directionincoming \| outgoing \| string \| nullAlways normalized to incoming or outgoing. Values like received or sent are translated before being emitted
filesunknown[]Attached files
imagesunknown[]Attached images
owner{ "id": string } \| nullMessage author. id is the operator's email when applicable
lastUpdatestring \| nullISO-8601 date
rolestring \| nulluser, assistant, tool, etc.
statusstring \| nullMessage status. Common values: received, sent, delivered, read, failed

Example of message.updated:

{
  "event": "message.updated",
  "resourceType": "message",
  "resourceId": "msg_789",
  "changes": {
    "status": {
      "before": "sent",
      "after": "read"
    }
  },
  "data": {
    "client": {},
    "conversation": {},
    "message": {}
  }
}

Comments

Events:

Eventchanges
comment.creatednull
comment.updatedPer-field changes with { "before", "after" }: content, contentType, attachments, isSpam, moderation
comment.deletednull

For comment.* events, resourceType is comment and data always contains client, post, thread, and comment. client is a slim summary when the author is associated with a customer, or null otherwise. The platform is available at data.comment.platform.

Example of 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": "Which color do you prefer this summer?",
      "permalink": "https://www.instagram.com/p/DemoPost123/",
      "contactName": "Platica Demo"
    },
    "thread": {
      "id": "comment_demo_01",
      "status": "open",
      "needsReply": true,
      "commentCount": 1,
      "topic": "Instagram comments",
      "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": "I love the blue one 💙",
      "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 summarizes the related post:

FieldTypeDescription
idstring \| nullPost identifier
externalIdstring \| nullContent identifier on the platform
platformstring \| nullinstagram, facebook, or another supported platform
channelIdstring \| nullConnected channel
titlestring \| nullPost text or title
permalinkstring \| nullPublic URL of the post
contactNamestring \| nullDisplay name for the channel account

thread summarizes the comment thread:

FieldTypeDescription
idstring \| nullThread identifier; matches comment.rootCommentId
statusstring \| nullCurrent thread status
needsReplybooleanWhether the thread needs a reply
commentCountnumberNumber of comments in the thread
topicstring \| nullThread topic
tagsstring[]Thread tags

comment contains the affected comment:

FieldTypeDescription
idstring \| nullComment identifier
postIdstring \| nullExternal post identifier; matches post.externalId
parentCommentIdstring \| nullParent comment when this is a reply
rootCommentIdstring \| nullRoot comment in the thread
permalinkstring \| nullPublic URL of the comment
platformstring \| nullComment platform
channelIdstring \| nullConnected channel
contentstringComment text
contentTypestringContent type, usually text
attachmentsobject[]Public comment attachments
creationDatestring \| nullISO-8601 creation date
lastUpdatestring \| nullISO-8601 last update date
directionincoming \| outgoing \| nullincoming for customers and outgoing for the brand
rolestring \| nullNormalized author role
clientIdstring \| nullAssociated customer, when available
authorNamestring \| nullAuthor display name
authorPhotoUrlstring \| nullURL of the author's public photo
authorTypestring \| nullAuthor type
isSpambooleanWhether the comment is marked as spam
moderation{ "action": "none" \| "hidden" \| "deleted", "synced": boolean, "moderatedAt": string \| null }Moderation state

Each attachments item can include:

FieldTypeDescription
idstring \| nullAttachment identifier
typestringimage, video, link, or unsupported
statusstringAttachment availability status
urlstring \| nullURL available to the integration
mimestring \| nullMIME type, when available
titlestring \| nullAttachment title
bytesnumber \| nullSize in bytes, when available

In comment.updated, each changed field includes its previous and new values. data keeps the same shape and contains the snapshot after the change. For example, the changes fragment for a moderation update can be:

{
  "moderation": {
    "before": {
      "action": "none",
      "synced": false,
      "moderatedAt": null
    },
    "after": {
      "action": "hidden",
      "synced": true,
      "moderatedAt": "2026-08-31T15:48:02.000Z"
    }
  }
}

Customers

Events:

Eventchanges
client.creatednull
client.updatedPer basic-field changes: 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": { "<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,
  "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
}

Blocking and moderation

status and moderation always travel inside data, and moderation is returned in full even when the customer has no incidents. Its fields are documented in Get a Customer .

When you block or unblock a customer you get a client.updated carrying changes.status, plus one conversation.status.updated per open conversation that was synced. If you only care about the block itself, filter by the event's source rather than by how many events arrived.

Actionsource on the client.updated
Block via the APIapi.client.blocked
Unblock via the APIapi.client.unblocked
Strike reset via the APIapi.client.strikes_reset
Block / unblock from the dashboarddashboard.client.blocked, dashboard.client.unblocked
Strike reset from the dashboarddashboard.client.strikes_reset
Automatic block by moderationsystem.moderation.applied
Conversation marked as spamapi.conversation.status_changed

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:

EventchangesresourceType
referral.receivednullclient

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"
    }
  }
}
FieldTypeDescription
platformstringChannel platform: whatsapp, messenger, instagram
sourcestring \| nullSource type reported by Meta, usually ad
ad_idstring \| nullMeta ad identifier
ctwa_clidstring \| nullClick-to-WhatsApp click ID, useful for the Conversions API
refstring \| nullAd ref parameter (Click-to-Messenger / m.me) when present
headlinestring \| nullAd headline
bodystring \| nullAd primary text
media_typestring \| nullAd media type: image, video, etc.
image_urlstring \| nullAd image URL
video_urlstring \| nullAd video URL
thumbnail_urlstring \| nullAd thumbnail URL
welcome_messagestring \| nullWelcome message prefilled in the ad
user_textstring \| nullText the customer sent when starting the conversation
channelIdstring \| nullChannel where the message arrived
messageIdstring \| nullIdentifier of the inbound message that triggered the event
conversationIdstring \| nullConversation identifier
receivedAtstring \| nullISO-8601 date when the attributed message was received