# message.received

A new message arrived in the inbox; `message.text`, `message.attachments[]` and `message.sender` describe it.

### Standby

While another app owns the conversation it carries `metadata.standby: true`. Zernio runs no workflow or keyword automation on it. A bot must skip standby messages, or on WhatsApp it takes the conversation from the agent.

### Interactive replies

Button and list taps, flow responses and orders arrive under `metadata` (`interactiveType`, `interactiveId`, `flowResponseData`, `order`). Only a referral that arrives without a message becomes `referral.received`.

### Platform notes

New email from Google and Microsoft mailboxes arrives as this event.




**Payload for `message.received`:**

- **id** (required) `string`: Stable webhook event ID: the dedupe key, also sent as the X-Zernio-Event-Id header and identical on every retry and redelivery. It identifies the event only, never an account or other resource.
- **profiles**: `WebhookProfiles` - See schema definition
- **test** `boolean`: Always true when present: only a sample sent by POST /v1/webhooks/test with an event carries it. Real deliveries never do.
- **event** (required) `string`: No description - one of: message.received
- **message** (required) `object`: 
  - **id** (required) `string`: Internal message ID
  - **conversationId** (required) `string`: Internal conversation ID
  - **platform** (required) `string`: No description - one of: instagram, facebook, telegram, whatsapp, sms, rcs, twitter, bluesky, reddit, slack, tiktok, imessage
  - **platformMessageId** (required) `string`: Platform's message ID
  - **direction** (required) `string`: No description - one of: incoming, outgoing
  - **text** (required) `string,null`: Message text content
  - **attachments** (required) `array[object]`: 
    - **type** (required) `string`: Attachment type (image, video, file, sticker, audio, share).

Instagram and Facebook deliver a shared reel, post or ad with no type of its own (Meta's `unsupported_type`). Zernio resolves it from the media's Content-Type at ingest, so it arrives as `image`, `video`, `audio` or `file` with `originalType: "unsupported_type"`.

`type: "unsupported_type"` itself is emitted only when the CDN could not be classified in time; the `url` still works.

    - **originalType** `string`: Instagram and Facebook only, and present only when it differs
from `type`. Meta's own attachment type before Zernio normalized
it: `ig_reel` and `reel` become `video`, while `ig_post`, `post`,
`ig_story` and `story_mention` all become `share`.

Read it before rendering, because `type: "share"` alone is
ambiguous. In particular a story mention arrives as
`type: "share"` with `originalType: "story_mention"`; treating an
unrecognized type as a generic document shows your agent
"document received" for what is usually a lead.

`originalType: "unsupported_type"` marks a share Meta did not
classify, resolved by content type. **Download it on receipt**:
Meta answers `is_unsupported` with no attachments when the
message node is read back, so the attachment-resolve endpoint
cannot re-mint this url once it expires.

    - **mimeType** `string`: MIME type of the media when Zernio knows it. On Instagram and
Facebook it is set for shares resolved by content type
(`originalType: "unsupported_type"`).

    - **url** (required) `string`: Where to fetch the attachment. **The contract differs by platform.**

- **WhatsApp**: points at `GET /v1/whatsapp/media/{mediaId}`, an authenticated Zernio endpoint. You MUST send `Authorization: Bearer <your API key>`; without it you get `401`. Download the bytes when this webhook arrives: Meta drops inbound media after a limited retention window, then the endpoint answers `400` permanently.
- **Instagram / Facebook / Telegram**: a direct platform CDN link that needs no authentication and expires on the platform's own schedule.

**Webhook attachments carry no `refreshUrl`.** It is stamped only when you read a message over REST (`GET /v1/inbox/conversations/{conversationId}/messages`).

On Instagram and Facebook the url is a signed, expiring Meta CDN link, so do not persist it. Store the message id and resolve media through `GET /v1/inbox/conversations/{conversationId}/messages/{messageId}/attachments/{index}?accountId={accountId}`, which re-mints it on demand.

Every value that URL needs is in this payload: `message.conversationId`, `message.platformMessageId`, `account.accountId`, and the attachment's zero-based position in this array.

    - **payload** `object`: Additional attachment metadata
      - **voice** `boolean`: WhatsApp audio only. True for a voice note recorded in the WhatsApp client, false for an audio file.
  - **sender** (required) `object`: 
    - **id** (required) `string`: Sender's platform identifier. For WhatsApp this is the phone
number (without leading `+`) when available, otherwise the
`businessScopedUserId`.

    - **contactId** `string`: Zernio CRM Contact id for this sender, when one exists (omitted for outgoing/business sender).
    - **name** `string`: No description
    - **username** `string`: No description
    - **picture** `string`: No description
    - **phoneNumber** `string,null`: WhatsApp only. Sender's phone number in E.164 format (with leading `+`).

**Nullable during the BSUID rollout (April 2026+).** WhatsApp
users who adopt a username can message businesses without
exposing a phone number, so this field is omitted for them.
Match by `businessScopedUserId` instead. See
`libs/whatsapp/docs/whatsapp-bsuid-migration.md`.

    - **businessScopedUserId** `string`: WhatsApp only. Business-scoped user ID (BSUID), Meta's canonical
identifier for a WhatsApp user within your business. Present
when Meta includes it in the inbound payload (rollout in
progress since early April 2026). **Recommended primary identity
anchor** going forward; fall back to `phoneNumber` only when
this field is absent.

    - **parentBusinessScopedUserId** `string`: WhatsApp only. Parent BSUID for businesses with linked business
portfolios. Omitted for standalone portfolios.

    - **whatsappUsername** `string`: WhatsApp only. User's WhatsApp username (e.g. `jane.shop`). Not a
stable identifier, because users can change it. Useful for display,
not recommended as an identity anchor.

    - **instagramProfile** `object`: Instagram profile data for the sender. Only present for Instagram conversations.
      - **isFollower** `boolean,null`: Whether the sender follows your Instagram business account
      - **isFollowing** `boolean,null`: Whether your Instagram business account follows the sender
      - **followerCount** `integer,null`: The sender's follower count on Instagram
      - **isVerified** `boolean,null`: Whether the sender is a verified Instagram user
  - **sentAt** (required) `string` (date-time): 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; some platforms (e.g. WhatsApp and Telegram) report whole seconds.

Use it as the chronological ordering key. If two messages share a value, fetch the conversation messages with sortOrder=desc for the deterministic order.

  - **isRead** (required) `boolean`: No description
  - **sentVia** `string,null`: Which Zernio surface produced the message. Always present and
always `null` on this event, since nobody on our side produced an
inbound message; it is only informative on `message.sent`, which
documents the vocabulary.
 - one of: human, api, broadcast, sequence, workflow, comment_automation, bulk-api, 
  - **automationId** `string,null`: Always null on this event; see message.sent.
  - **workflowId** `string,null`: Always null on this event; see message.sent.
  - **executionId** `string,null`: Always null on this event; see message.sent.
  - **broadcastId** `string,null`: Always null on this event; see message.sent.
  - **sequenceId** `string,null`: Always null on this event; see message.sent.
- **conversation** (required): `InboxWebhookConversation` - See schema definition
- **account** (required): `InboxWebhookAccount` - See schema definition
- **metadata** `object,null`: Platform-specific message context (present when the message is a quick reply tap, postback button tap, inline keyboard callback, a quote-reply to an earlier message, an inbound another app is answering under Meta's handover protocol, or a TikTok DM that is not plain text)
- **timestamp** (required) `string` (date-time): 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.