For media messages (image, video, audio, document, sticker), the content.mediaId is provided initially. The media is then downloaded and stored, and subsequent reads of the message will include a content.mediaUrl with the permanent URL.
When a customer replies to (quotes) an earlier message, the content object carries the reply context so you can thread the conversation. When the quoted message exists in Zavu, you get its Zavu replyToMessageId, a text snippet, and its type:
{ "id": "evt_1705312300000_rep123", "type": "message.inbound", "timestamp": 1705312300000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "messageId": "msg_reply999", "from": "+14155551234", "to": "+13125559876", "channel": "whatsapp", "messageType": "text", "text": "Yes, that order is correct", "content": { "replyToMessageId": "msg_original123", "replyToProviderMessageId": "wamid.HBgL...ORIGINAL123", "replyToFrom": "+13125559876", "replyToText": "Your order #ORD-12345 has shipped.", "replyToMessageType": "text" }, "profileName": "John Doe" }}
If the quoted message is not stored in Zavu (e.g. an old or unknown message), the context degrades gracefully — you still get the provider ID and sender, but replyToMessageId, replyToText, and replyToMessageType are omitted:
Use replyToMessageId to link the reply to a message in your system. When it’s absent, fall back to replyToProviderMessageId (the WhatsApp WAMID), which is always present on a reply.
Message text or media caption (also button/list reply title for interactive messages)
content
object | null
Additional content (for non-text messages)
content.mediaId
string
Media ID from WhatsApp (media messages)
content.mimeType
string
MIME type of the media
content.filename
string
Filename (for documents)
content.latitude
number
Latitude coordinate (location messages)
content.longitude
number
Longitude coordinate (location messages)
content.name
string
Location name (location messages)
content.address
string
Location address (location messages)
content.contacts
array
Contact cards (contact messages)
content.interactiveReply
object
Interactive reply details (when user clicks a button or list item)
content.interactiveReply.type
string
button_reply or list_reply
content.interactiveReply.id
string
ID of the button/list item clicked
content.interactiveReply.title
string
Title of the button/list item clicked
content.replyToMessageId
string
Zavu ID of the quoted message. Only present when the quoted message exists in Zavu.
content.replyToProviderMessageId
string
Provider message ID (WhatsApp WAMID) of the quoted message. Always present on a reply.
content.replyToFrom
string
Sender of the quoted message (E.164 format).
content.replyToText
string
Truncated snippet of the quoted message text (≤200 chars). Empty when the quoted message has no text (e.g. media).
content.replyToMessageType
string
Type of the quoted message (text, image, video, etc.).
profileName
string | null
Sender’s WhatsApp profile name. null for SMS.
providerTimestamp
number | null
Provider’s original receive time (Unix ms) — when the channel received the message from the contact. Set for WhatsApp, Telegram, Instagram, and Messenger; null for SMS and email. Compare against the top-level timestamp (dispatch time) to detect delayed deliveries.
The top-level timestamp is when Zavu dispatched the webhook, not when the contact sent the message. If a channel delays delivery, these can differ by minutes. Use data.providerTimestamp to measure the real delay and skip stale messages:
const delaySeconds = (event.timestamp - event.data.providerTimestamp) / 1000if (event.data.providerTimestamp && delaySeconds > 120) { // Message arrived more than 2 minutes late — likely stale, skip it. return}
async function handleInboundMessage(data) { const { from, text, channel } = data; // Store the message await db.messages.create({ from, text, channel, receivedAt: new Date() }); // Send auto-reply if needed if (text.toLowerCase().includes("help")) { await zavu.messages.send({ to: from, text: "Thanks for reaching out! A support agent will contact you shortly." }); }}
Triggered when a customer sends a message type that is not supported by WhatsApp Cloud API. This includes polls, reactions, deleted messages, ephemeral messages, and third-party stickers.
async function handleUnsupportedMessage(data) { const { from, messageId } = data; // Log unsupported message for analytics await db.unsupportedMessages.create({ messageId, from, receivedAt: new Date() }); // Optionally notify the customer await zavu.messages.send({ to: from, text: "We received your message, but this message type is not supported. Please send a text message instead." });}
Triggered when your outbound message is successfully accepted by the carrier or Meta. This confirms the message has left Zavu’s systems and is being processed by the provider.
When using WhatsApp in coexistence mode, messages sent directly from the WhatsApp Business App also trigger message.sent events. These events include the full message content so you can sync outbound messages that were not sent through the Zavu API.You can distinguish these from regular status updates by checking the source field.
{ "id": "evt_1705312200000_echo456", "type": "message.sent", "timestamp": 1705312200000, "senderId": "snd_abc123", "projectId": "prj_xyz789", "data": { "messageId": "msg_echo789", "from": "+13125559876", "to": "+14155551234", "channel": "whatsapp", "messageType": "text", "status": "delivered", "text": "Hi! Thanks for reaching out. We'll get back to you shortly.", "source": "whatsapp_business_app", "direction": "outbound" }}
When you receive a message.sent event with source: "whatsapp_business_app", you should create a new outbound message record in your system rather than treating it as a status update. These messages were sent outside of Zavu’s API and have their own unique messageId.
Delivery confirmation availability depends on the carrier and channel. WhatsApp provides reliable delivery receipts, while SMS delivery confirmations vary by carrier.
async function handleInvitationStatusChanged(data) { const { invitationId, clientName, currentStatus, senderId } = data; if (currentStatus === "completed") { // Client successfully onboarded await notifyTeam({ message: `${clientName} has connected their WhatsApp!`, senderId, }); // Create templates for the new client await createDefaultTemplates(senderId); } else if (currentStatus === "in_progress") { // Client started the process console.log(`${clientName} is completing WhatsApp setup`); }}