Webhook and WebSocket share the same envelope and the same catalog of events. The only difference is the delivery channel, data is identical.
This page documents the 6 types: message.exchange , message.status , call.update , group.flow , instance.state and label.update .
Envelope
{
"event" : "<event-name>" ,
"data" : { /* event-specific payload */ },
"instanceData" : {
"baseUrl" : "https://api.example.com" ,
"instance" : "<name>" ,
"token" : "<instance-token>"
}
}
Filtering is done by the event name in the events field of the config. Empty = all types.
Filtering and routing
{ "events" : [ "message.exchange" , "call.update" , "instance.state" ] }
When byEvents=true (webhook only), the event name is appended to the URL :
Config: url: "https://app/wh", byEvents: true
Delivery: POST https://app/wh/message.exchange
Useful for endpoint-based routing without inspecting the payload.
message.exchange
Messages sent and received (text, media, sticker, document, audio, poll, contact, location, etc.), edits and revocations.
Payload
{
"id" : "wamid.msg.123" ,
"message" : {
"id" : "wamid.msg.123" ,
"direction" : "incoming | outgoing" ,
"timestamp" : "2026-04-28T10:30:00Z" ,
"chat" : {
"jid" : "5511999999999" ,
"lid" : "100@s.whatsapp.net" ,
"name" : "João Silva" ,
"type" : "private | group | newsletter" ,
"isCommunity" : true
},
"sender" : {
"jid" : "5511999999999" ,
"lid" : "100@s.whatsapp.net" ,
"name" : "João Silva"
},
"content" : { "text" : "Hello" },
"media" : {
"type" : "image | video | audio | document | sticker | ptt | ptv" ,
"url" : "https://media-...whatsapp.net/..." ,
"s3Url" : "https://bucket.s3.amazonaws.com/..." ,
"base64" : "iVBORw0KGgo..." ,
"mimetype" : "image/jpeg" ,
"size" : 45678 ,
"caption" : "Event photo" ,
"fileName" : "doc.pdf"
},
"edit" : {
"original_id" : "wamid.original" ,
"originalContent" : "Old text" ,
"text" : "New text"
},
"forward" : { "count" : 2 },
"reply" : {
"message_id" : "wamid.replied" ,
"sender" : { "jid" : "..." , "name" : "..." },
"text" : "..." ,
"media" : { /* simplified media object */ }
},
"poll" : {
"title" : "Which flavor?" ,
"options" : [{ "name" : "Chocolate" , "count" : 0 }]
},
"location" : {
"latitude" : -23.5505 ,
"longitude" : -46.6333 ,
"address" : "Av. Paulista, 1374"
},
"contact" : {
"display_name" : "Maria" ,
"phone_number" : "5511987654321" ,
"vcard" : "BEGIN:VCARD..."
},
"list_response" : {
"title" : "..." ,
"body" : "..." ,
"list_type" : "single_select" ,
"single_select_reply" : { "option_name" : "p1" }
},
"button_response" : {
"title" : "Buy" ,
"body" : "..." ,
"selected_button_id" : "buy_camiseta"
},
"interactive" : { /* form / native flow / other surfaces */ }
}
}
Conditional fields
Only the fields relevant to the message type are populated. Edits have edit populated; revocations arrive with type: "message_revoke" in data.message.type.
chat.isCommunity appears only when true , indicating the chat is the announcement channel (parent / announcement channel) of a WhatsApp community. Subgroups linked to a community keep type: "group" and do not include the isCommunity field. In regular groups and DMs the field is also omitted.
media.base64 only appears when mediaBase64=true in the config (webhook or WebSocket). Otherwise, use media.url (whatsapp.net, expires) or media.s3Url (if S3 is configured on the instance).
Example (image received)
{
"event" : "message.exchange" ,
"data" : {
"id" : "wamid.123" ,
"message" : {
"id" : "wamid.123" ,
"direction" : "incoming" ,
"timestamp" : "2026-04-28T10:30:00Z" ,
"chat" : { "jid" : "5511999999999" , "name" : "João" , "type" : "private" },
"sender" : { "jid" : "5511999999999" , "name" : "João" },
"media" : {
"type" : "image" ,
"url" : "https://media-abc.whatsapp.net/..." ,
"mimetype" : "image/jpeg" ,
"size" : 45678 ,
"caption" : "Photo"
}
}
},
"instanceData" : { "baseUrl" : "https://api..." , "instance" : "minha" , "token" : "..." }
}
message.status
Delivery receipts: delivered, read, played, etc.
Payload
{
"status" : "delivered | read | played | sender | read_self | played_self | retry | inactive | server_error" ,
"messageIds" : [ "wamid.123" , "wamid.124" ],
"timestamp" : "2026-04-28T10:45:00Z" ,
"chat" : {
"jid" : "5511999999999" ,
"type" : "private | group | broadcast" ,
"isCommunity" : true
},
"recipient" : "5511999999999" ,
"messageSender" : null ,
"isFromMe" : false
}
status enum
Value Meaning deliveredMessage reached the recipient’s device. readRecipient read it (read receipts on). playedAudio/voice note was played. senderInternal echo (the source itself reporting delivery). read_selfThe user themselves marked it as read on another device. played_selfThe user themselves played it on another device. retryServer requested redelivery (transient). inactiveRecipient has been offline too long. server_errorGeneric WhatsApp server error.
messageSender in groups : JID of the original message author (relevant when someone reads a message from another participant).
chat.isCommunity follows the same rule as message.exchange: present and true only when the chat is the announcement channel of a community.
call.update
Call events: offer, accepted, rejected, terminated, latency.
Payload
{
"type" : "offer | accepted | rejected | terminated | notification | latency" ,
"direction" : "incoming | outgoing" ,
"callId" : "call-abc123" ,
"from" : "5511999999999" ,
"to" : "5511888888888" ,
"timestamp" : "2026-04-28T10:35:00Z" ,
"groupJid" : null ,
"callMedia" : "audio | video | null" ,
"remotePlatform" : "Android | iPhone | null" ,
"remoteVersion" : "2.23.15.74" ,
"noticeType" : "group | null" ,
"reason" : null ,
"duration" : 123 ,
"latency" : 45.6 ,
"latencyStatus" : "Excellent | Good | Average | Poor"
}
type enum
Value When it fires offerCall received/sent (initial ring). acceptedRemote side answered. rejectedRemote side rejected. terminatedCall ended, duration in seconds is populated. notificationContextual call notification (e.g., missed group call). latencyLatency metric during the call (latency in ms, latencyStatus).
To auto-reject calls, set autoRejectCalls=true in the instance settings block , you still receive the offer + rejected events on the webhook.
group.flow
Group changes: members, metadata, settings.
Payload, participant change
{
"type" : "joined | left | promoted | demoted" ,
"groupJid" : "120363406289005073@g.us" ,
"groupName" : "Dev Team" ,
"timestamp" : "2026-04-28T11:00:00Z" ,
"participants" : [
{ "jid" : "5511999999999" , "action" : "joined" }
]
}
typeMeaning nameGroup name changed topicDescription changed locked / unlockedMembers can/cannot edit info announce / not_announceMembers can/cannot send messages ephemeral / not_ephemeralEphemeral messages on/off inviteInvite link generated link / unlinkSubgroup linked/unlinked from community deleteGroup deleted membership_approvalApproval mode for new members changed suspended / unsuspendedGroup suspended/reactivated by WhatsApp
instance.state
Changes in the instance’s own state (connection, QR, ban, pairing).
Payload
{
"instance" : "minha" ,
"state" : "connected | disconnected | logged_out | stream_replaced | temp_banned | client_outdated | connect_failure | stream_error | cat_refresh_error | qr_ready | pair_success | pair_error | qr_scanned_no_multidevice | keepalive_timeout | keepalive_restored | manual_reconnect" ,
"timestamp" : "2026-04-28T10:50:00Z" ,
"reason" : "..." ,
"reasonCode" : 123 ,
"message" : "..." ,
"expireAt" : "2026-04-28T11:50:00Z" ,
"expireInSeconds" : 3600 ,
"onConnect" : true ,
"codes" : [ "1@abc...,xyz==,base64string" ],
"jid" : "5511999999999@s.whatsapp.net" ,
"platform" : "iPhone | Android | Desktop" ,
"errorMsg" : "..."
}
state enum
State Meaning Extra fields populated connectedActive session, ready to send/receive. , disconnectedDisconnected (transient, usually reconnects on its own). , logged_outSession invalidated (requires new connect). reasonstream_replacedAnother session took over (multi-device conflict). , temp_bannedTemporary ban applied by WhatsApp. expireAt, expireInSeconds, reasonclient_outdatedThe WhatsApp Web version in use is outdated, contact support. messageconnect_failureFailure during connection. reason, reasonCodestream_errorWhatsApp stream error. errorMsgcat_refresh_errorFailed to refresh credentials (cat). errorMsgqr_readyNew QR available for pairing. codes[], onConnectpair_successPairing completed. jid, platformpair_errorError during pairing. errorMsgqr_scanned_no_multideviceQR scanned but the target device does not support multidevice. , keepalive_timeoutUnstable connection, keepalive not answered. , keepalive_restoredConnection stabilized after keepalive_timeout. , manual_reconnectReconnection triggered manually via REST. ,
So your client knows when to refresh the QR in the UI, listen for instance.state with state=qr_ready and render data.codes[0].
label.update
Label edits/associations (WhatsApp Business labels).
Payload
{
"type" : "edit | chat | message" ,
"labelId" : "1" ,
"timestamp" : "2026-04-28T10:00:00Z" ,
"action" : "updated | deleted | add | remove" ,
// type=edit:
"name" : "Important" ,
"color" : 0 ,
"labelType" : "CUSTOM" ,
"isActive" : true ,
"isImmutable" : false ,
"orderIndex" : 1 ,
"deleted" : false ,
// type=chat:
"chatJid" : "5511999999999@s.whatsapp.net" ,
"labeled" : true ,
// type=message:
"messageId" : "wamid.abc"
}
type × action combinations
typevalid action Extra fields editupdated, deletedname, color, labelType, isActive, isImmutable, orderIndex, deletedchatadd, removechatJid, labeledmessageadd, removechatJid, messageId
Events not emitted (internal)
Captured by the whatsmeow handler but not propagated via webhook/WS:
*events.Picture, profile picture change (logged only).
*events.FBMessage, Facebook Business (logged only).
*events.HistorySync, history sync (processed and stored in DB).
If you need to consume any of these changes, poll the corresponding REST endpoints (profile, history).
References
Configure webhook Filter events via events[] in the config.
Configure WebSocket Same filter syntax as the webhook.
Connect via WebSocket Receive events in real time.
Events overview Webhook vs WebSocket comparison.