phone_number_id at the top level.
Payload structure
Webhook payloads separate message data from conversation data:- message.kapso - Message-scoped only: direction, status, processing_status, statuses (raw status history), origin, has_media, content (text representation), transcript (for audio), media helpers (media_data, media_url, message_type_data)
- conversation - Top-level identifiers such as
id,contact_name,phone_number,phone_number_id, and, when available,business_scoped_user_id,parent_business_scoped_user_id, andusername. Optionalconversation.kapsocontains summary metrics only (counts, last-message metadata, timestamps) — it never containscontact_name - phone_number_id - Included at top level for routing
phone_number, from, to, or wa_id are always present.
Timestamps use two formats: conversation.kapso fields are UTC with microseconds (2025-10-28T17:25:01.000000Z), every other ISO timestamp carries a UTC offset (2025-10-28T14:25:01-03:00). message.timestamp is a Unix epoch string. Parse all three.
WhatsApp webhook events
Sent only to phone-number webhooks (/whatsapp/phone_numbers/{phone_number_id}/webhooks).
Message received
whatsapp.message.receivedFired when a new WhatsApp message is received from a customer. Supports message buffering for batch delivery.Message sent
whatsapp.message.sentFired when a message is successfully sent to WhatsAppMessage delivered
whatsapp.message.deliveredFired when a message is successfully delivered to the recipient’s deviceMessage read
whatsapp.message.readFired when the recipient reads your messageMessage failed
whatsapp.message.failedFired when a message fails to deliverConversation created
whatsapp.conversation.createdFired when a new WhatsApp conversation is initiatedConversation ended
whatsapp.conversation.endedFired when a WhatsApp conversation ends (agent action, manual closure, or 24-hour inactivity)Conversation inactive
whatsapp.conversation.inactiveFired when no messages (inbound/outbound) for configured minutes (1-1440, default 60)Contact identity changed
whatsapp.contact.identity_changedFired when a contact receives a new business-scoped user IDMarketing preference changed
whatsapp.contact.marketing_preference_changedFired when a contact stops or resumes marketing messages from one of your numbersPayload structures
Kapso sends the event name in theX-Webhook-Event header. The examples below show unbuffered request bodies. If buffering is enabled for whatsapp.message.received, the request body uses a batch envelope with type, batch: true, data: [...], and batch_info; each data item has the same shape as the unbuffered whatsapp.message.received payload below. See Batched webhook format for a full batched payload example.
whatsapp.message.received
whatsapp.message.sent
whatsapp.message.delivered
whatsapp.message.failed
whatsapp.conversation.created
whatsapp.conversation.ended
whatsapp.conversation.inactive
whatsapp.contact.identity_changed
contact holds the identity after the change; previous holds the business-scoped user IDs
Meta reported for the contact before it. Either previous value can be null when Meta does not
send it. Use it to re-key stored identifiers in your own system.
Fired for Meta’s user_changed_number and user_changed_user_id system messages. Those system
messages do not produce a whatsapp.message.received event. See
Business-scoped user IDs.
whatsapp.contact.marketing_preference_changed
status is stopped or resumed. previous_status is the state before this change, and is
null the first time Kapso hears about the contact’s preference. detail is the free-text
reason Meta sends, and can be null.
occurred_at is the timestamp WhatsApp reported for the change. Kapso only records preference
changes that carry one: an event Kapso cannot place in time cannot be ordered against what it
already knows, so it is rejected at ingest and raised for investigation rather than applied.
sequence increases with every preference change Kapso records, and is what you should compare
to decide which of two deliveries is newer. Deliveries are at-least-once and are not guaranteed
to arrive in order, so if you store the contact’s preference, keep the sequence you last
applied and only apply a delivery whose sequence is strictly greater — a redelivery of the
same event carries the same value, and applying it again would repeat your side effects. Do not
order on occurred_at alone: WhatsApp reports it to the second, so a rapid stop and resume can
share a value.
The preference is scoped to the phone number it arrived on: stopping marketing on one of your
numbers does not stop it on the others. The event only fires when the state actually changes, so
a repeated stop from Meta does not produce a second delivery.
Once a contact is stopped, Kapso refuses marketing template sends to them on that number with
error code marketing_preference_stopped. Utility and authentication templates and free-form
session messages are unaffected. Broadcasts mark those recipients suppressed and do not charge
for them. See Marketing opt-outs and
Broadcasts.
Multiple inactivity timeouts
Create separate webhooks for different timeout thresholds:Message origin
Themessage.kapso.origin field indicates how the message entered the system:
- cloud_api - Sent via Kapso API (outbound jobs, flow actions, API calls)
- business_app - Echoed from WhatsApp Business App (when using the Business App)
- history_sync - Backfilled during message history imports (only if project ran sync)
Status history
Themessage.kapso.statuses array contains the complete history of raw Meta status events for a message, ordered chronologically. Each entry is the unmodified payload from Meta’s webhook.
Status object structure
Each status object in the array follows Meta’s webhook format:
See Meta’s status webhook reference for the complete schema.
Use this field to track the full lifecycle of outbound messages and understand failure causes. The array only appears when status events have been recorded.
Message types
Themessage.type field can be one of:
text- Plain text messageimage- Image attachmentvideo- Video attachmentaudio- Audio/voice messagedocument- Document attachmentlocation- Location sharingtemplate- WhatsApp template messageinteractive- Interactive message (buttons, lists)reaction- Message reactioncontacts- Contact card sharing
Message type-specific data
Media messages (image/video/document)
Audio messages
Location messages
Template messages
Interactive messages
Reaction messages
Contact messages
When you send a request contact info CTA and the user taps it, WhatsApp sends acontacts message. It includes the user’s phone number and keeps BSUID identity when available.
origin is other and the payload can include a vCard.
