Skip to main content

Webhook Events & Payloads

This page shows the exact JSON payload format for each webhook event type.

Message status events

These events fire when an outbound message changes status: message.sent, message.delivered, message.read, message.failed, button.clicked.

{
"event": "message.delivered",
"timestamp": "2026-06-15T10:05:00",
"campaign_id": "507f1f77bcf86cd799439060",
"campaign_name": "Summer Promo",
"message": {
"message_id": "507f1f77bcf86cd799439070",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"status": "delivered",
"previous_status": "sent",
"status_timestamp": "2026-06-15T10:05:00"
}
}

Fields

FieldTypeDescription
eventstringEvent type (e.g., message.delivered)
timestampstringISO 8601 timestamp of the event
campaign_idstring | nullCampaign ID (null if sent via API or chat)
campaign_namestring | nullCampaign name
message.message_idstringInternal message ID
message.contact_phonestringRecipient's phone number
message.contact_namestring | nullRecipient's display name
message.statusstringNew status
message.previous_statusstring | nullPrevious status
message.status_timestampstringWhen the status changed

Additional fields for message.failed

When a message fails, the payload includes two extra fields:

FieldTypeDescription
message.error_messagestringHuman-readable error description
message.error_codenumberWhatsApp error code
{
"event": "message.failed",
"timestamp": "2026-06-15T10:05:00",
"campaign_id": "507f1f77bcf86cd799439060",
"campaign_name": "Summer Promo",
"message": {
"message_id": "507f1f77bcf86cd799439070",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"status": "failed",
"previous_status": "sent",
"status_timestamp": "2026-06-15T10:05:00",
"error_message": "Message undeliverable",
"error_code": 131026
}
}

Inbound message event

Fires when a contact sends you a new message: message.received.

{
"event": "message.received",
"timestamp": "2026-06-15T10:15:00",
"message": {
"message_id": "wamid.HBgNOTE4MDMxMjM0NTY3OQ==",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"content": "Hi, I have a question",
"message_type": "text",
"timestamp": "2026-06-15T10:15:00",
"waba_phone_id": "123456789012345",
"is_reply": false,
"is_from_ad": false
}
}

Fields

FieldTypeDescription
message.message_idstringWhatsApp message ID
message.contact_phonestringSender's phone number
message.contact_namestring | nullSender's display name
message.contentstringMessage text content
message.message_typestringtext, image, video, audio, document, sticker, location, etc.
message.timestampstringWhen the message was received
message.waba_phone_idstringWhich WhatsApp number received the message
message.is_replybooleanWhether this is a reply to one of your messages
message.original_message_idstringOriginal message ID (only when is_reply is true)
message.media_urlstringMedia URL (only for media messages)
message.is_from_adbooleanWhether the message came from an ad click
message.referralobjectAd referral data (only when is_from_ad is true)

Reply event

Fires when a contact replies to one of your outbound messages: message.reply.

{
"event": "message.reply",
"timestamp": "2026-06-15T10:10:00",
"campaign_id": "507f1f77bcf86cd799439060",
"campaign_name": "Summer Promo",
"original_message": {
"message_id": "507f1f77bcf86cd799439070",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"sent_at": "2026-06-15T10:00:00"
},
"reply": {
"message_id": "507f1f77bcf86cd799439071",
"contact_phone": "911234567890",
"contact_name": "John Doe",
"content": "Yes, I'd like to know more!",
"message_type": "text",
"timestamp": "2026-06-15T10:10:00"
}
}

Fields

FieldTypeDescription
original_message.message_idstringID of the outbound message being replied to
original_message.contact_phonestringRecipient's phone number
original_message.contact_namestring | nullRecipient's display name
original_message.sent_atstringWhen the original message was sent
reply.message_idstringReply message ID
reply.contact_phonestringReplier's phone number
reply.contact_namestring | nullReplier's display name
reply.contentstringReply text content
reply.message_typestringtext, image, video, audio, document
reply.timestampstringWhen the reply was received
reply.media_urlstringMedia URL (only for media replies)

Conversation assignment events

Fire when the ownership of a chat changes: conversation.assigned, conversation.unassigned, conversation.resolved, conversation.reopened. They share one payload shape (with an actor describing who or what made the change), so a CRM can keep its own "who's handling this lead" view in sync.

{
"event": "conversation.assigned",
"timestamp": "2026-06-17T10:05:00.123456+00:00",
"conversation": {
"contact_phone": "911234567890",
"waba_phone_id": "123456789012345",
"status": "open",
"assigned_to": "507f1f77bcf86cd799439011",
"assigned_to_name": "Priya Sharma",
"previous_assignee_id": "507f1f77bcf86cd799439022",
"previous_assignee_name": "Rahul Verma",
"is_reassignment": true,
"note": "Escalated from tier 1",
"reason": null,
"actor": {
"user_id": "507f1f77bcf86cd799439033",
"name": "Anita Desai",
"source": "manual"
}
}
}

Fields

FieldTypeDescription
eventstringconversation.assigned, conversation.unassigned, conversation.resolved, or conversation.reopened
timestampstringISO 8601 timestamp, UTC with a +00:00 offset
conversation.contact_phonestringThe customer's phone number (digits only). Identifies the conversation.
conversation.waba_phone_idstring | nullThe WhatsApp number that owns this conversation
conversation.statusstring | nullConversation status at the time of the event (open / resolved)
conversation.assigned_tostring | nulluser_id of the agent the chat is now assigned to (null for unassigned)
conversation.assigned_to_namestring | nullDisplay name of the new assignee
conversation.previous_assignee_idstring | nulluser_id of the prior assignee (for reassignments / unassign)
conversation.previous_assignee_namestring | nullDisplay name of the prior assignee
conversation.is_reassignmentbooleantrue when an already-assigned chat moved to a different agent
conversation.notestring | nullFree-text handoff note, when one was provided
conversation.reasonstring | nullOptional reason (e.g. on resolve)
conversation.actorobjectWho or what made the change — see below

The actor object

FieldTypeDescription
actor.user_idstring | nullThe acting user's ID. For automatic changes this is literally the string "system".
actor.namestring | nullHuman-readable actor name (e.g. the agent's name, or System (round-robin))
actor.sourcestringWhere the change came from — use this to suppress echo loops

actor.source values:

SourceMeaning
manualSomeone changed it from the inbox
apiChanged via the assignment API
auto_replyAuto-assigned to an agent when they sent the first reply
auto_inboundAuto-assigned by round-robin routing on an inbound message
auto_reopenA resolved chat was reopened automatically by a customer reply

When each event fires

EventFires when
conversation.assignedA chat is assigned or reassigned to an agent (manually, via the API, or by auto-routing)
conversation.unassignedA chat is released back to the unassigned pool
conversation.resolvedA chat is marked resolved
conversation.reopenedA resolved chat is reopened — manually, via the API, or automatically when the customer replies
Timestamp format

conversation.* timestamps are timezone-aware and carry a +00:00 offset. The message.* payloads above use a naive UTC timestamp with no offset — treat both as UTC.


Test event

Sent when you click Test in the dashboard: webhook.test.

{
"event": "webhook.test",
"timestamp": "2026-06-15T10:00:00",
"message": "Test webhook message",
"endpoint_id": "507f1f77bcf86cd799439080",
"endpoint_name": "My Endpoint",
"user_id": "507f1f77bcf86cd799439090"
}