---
title: "Message sent event"
method: POST
path: "message.sent"
tags: ["Webhook Events"]
---

# Message sent event

`POST message.sent` (webhook)

Fired when a message is sent via the API, or from the WhatsApp Business app on
Coexistence numbers. Sends that carry platform-specific context deliver it under
`metadata`, so a quote-reply arrives with `metadata.quotedMessageId` and mirroring
CRMs can thread it without a lookup.

## Payload

- WebhookPayloadMessageSent — Webhook payload for message sent events (fired when a message is sent via the API, or from the WhatsApp Business app on Coexistence numbers)
  - `id` string, required — Stable webhook event ID
  - `event` 'message.sent', required
  - `message` object, required
    - `id` string, required — Internal message ID
    - `conversationId` string, required — Internal conversation ID
    - `platform` 'instagram' | 'facebook' | 'telegram' | 'whatsapp' | 'twitter' | 'reddit' | 'bluesky' | 'slack', required — Every platform whose outgoing messages Zernio observes. sms is absent on purpose: its carrier receipts update delivery status and never raise message.sent.
    - `platformMessageId` string, required — Platform's message ID
    - `direction` 'incoming' | 'outgoing', required
    - `text` string, nullable, required — Message text content
    - `attachments` object[], required
      - `type` string, required — Attachment type (image, video, file, sticker, audio)
      - `url` string, required — Where to fetch the attachment. For outgoing messages this is the media URL as sent, so for WhatsApp it is the URL you supplied when publishing (WhatsApp sends media by link), not a Zernio endpoint, and it needs no Zernio credentials. Contrast the inbound direction: `message.received` attachment URLs on WhatsApp point at the authenticated `GET /v1/whatsapp/media/{mediaId}`.
      - `payload` object — Additional attachment metadata
    - `sender` object, required
      - `id` string, required
      - `contactId` string — Always omitted on this event: the sender is the business, not a contact. Use conversation.contactId to join back to the CRM Contact.
      - `name` string
      - `username` string
      - `picture` string
    - `sentAt` string, date-time, required — When the message was sent, as reported by the platform and passed through unmodified. Full ISO 8601 date-time: Instagram and Facebook carry millisecond precision, while some platforms (for example WhatsApp and Telegram) report whole seconds. Use this field as the chronological ordering key. If two messages share the same value, fetch the conversation messages with sortOrder=desc for the deterministic order.
    - `isRead` boolean, required
    - `source` 'whatsapp_business_app' | 'cloud_api' — WhatsApp send origin. whatsapp_business_app when sent from the WhatsApp Business phone app on a Coexistence number; cloud_api when sent through Zernio (dashboard, API, or broadcasts). Absent on non-WhatsApp platforms. Says where WhatsApp saw the send come from, not which Zernio surface produced it: read sentVia for that.
    - `sentVia` 'human' | 'api' | 'broadcast' | 'sequence' | 'workflow' | 'comment_automation' | 'bulk-api' | 'null', nullable — Which Zernio surface produced this message: `human` (an operator in the Zernio inbox), `api` (a call to this API), `broadcast`, `sequence`, `workflow`, `comment_automation`, or `bulk-api` (POST /v1/whatsapp/bulk). Same vocabulary as the `source` filter on the inbox analytics endpoints, and the same value a later GET on this message returns. Always present, and `null` whenever the lineage is unknown: a message sent from the platform's own app, and every message stored before this field shipped (2026-08). Existing messages are NOT backfilled, so treat `null` as "unknown", never as "sent by a human".
  - `conversation` InboxWebhookConversation, required — The conversation context included in inbox webhook payloads.
    - `id` string, required
    - `platformConversationId` string, required
    - `participantId` string
    - `participantName` string
    - `participantUsername` string
    - `participantPicture` string
    - `status` 'active' | 'archived', required
    - `contactId` string — Zernio CRM Contact ID for the participant, when one exists. Resolved by joining `participantId` to the ContactChannel collection. Best-effort: omitted when no channel matches or `participantId` is absent. Lets integrators join any inbox webhook back to the CRM Contact without needing to look at the sender — which matters for outgoing and delivery-status events whose sender is the business.
  - `account` InboxWebhookAccount, required — The account context included in inbox webhook payloads.
    - `id` string, required — Social account ID
    - `accountId` string — Social account ID (same value as id). Canonical field so consumers can filter every webhook event on one field (e.g. route staging vs production by account). id is kept for backward compatibility.
    - `profileId` string — Zernio profile (workspace) ID this account belongs to. Use it to route or filter inbox webhooks by workspace. This is the profile ID only, not its name (resolve the name via the API with this ID). Optional; omitted on the shared WhatsApp sandbox account and when the account has no resolvable profile.
    - `platform` string, required
    - `username` string, required
    - `displayName` string
  - `metadata` object — Platform-specific context for the sent message. The key is present only when the send carried some context, and absent otherwise: it is never null and never an empty object.
    - `quotedMessageId` string — platformMessageId of the message this send is a quote-reply to. Set when the reply was sent through Zernio with `replyTo` on the inbox send API (WhatsApp and Telegram), and when the operator replied from the native WhatsApp Business, Instagram or Messenger app. WhatsApp API sends carry it on the event fired from the delivery status, so it arrives on the same `message.sent` as any other WhatsApp send.
    - `threadTs` string — Slack only. Parent thread ts of the sent message. Pass it back as `replyTo` on the inbox send API to keep replying inside the thread.
  - `timestamp` string, date-time, required — UTC time at which Zernio generated this event (set once when the event payload is built, before delivery is queued). Retries and redeliveries keep the original value, so it reflects the event, not the delivery attempt.

## Acknowledgement `200`

Webhook received successfully

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/zernio/zernio-api/revisions/dd66917efd2e/schema)
