Skip to main content
All webhook payloads use v2 format with 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, and username. Optional conversation.kapso contains summary metrics only (counts, last-message metadata, timestamps) — it never contains contact_name
  • phone_number_id - Included at top level for routing
Do not assume 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 WhatsApp

Message delivered

whatsapp.message.deliveredFired when a message is successfully delivered to the recipient’s device

Message read

whatsapp.message.readFired when the recipient reads your message

Message failed

whatsapp.message.failedFired when a message fails to deliver

Conversation created

whatsapp.conversation.createdFired when a new WhatsApp conversation is initiated

Conversation 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 ID

Marketing preference changed

whatsapp.contact.marketing_preference_changedFired when a contact stops or resumes marketing messages from one of your numbers

Payload structures

Kapso sends the event name in the X-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:
Each webhook fires independently when its threshold is reached.

Message origin

The message.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

The message.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

The message.type field can be one of:
  • text - Plain text message
  • image - Image attachment
  • video - Video attachment
  • audio - Audio/voice message
  • document - Document attachment
  • location - Location sharing
  • template - WhatsApp template message
  • interactive - Interactive message (buttons, lists)
  • reaction - Message reaction
  • contacts - 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 a contacts message. It includes the user’s phone number and keeps BSUID identity when available.
If the user shares a contact directly from WhatsApp instead, origin is other and the payload can include a vCard.