Conversations & Messages

Conversations are automatically created when a message is sent or received. Each conversation is scoped to a connected phone inbox and a contact identity. contactPhone remains available for legacy integrations, but new integrations should use contactIdentifier and contactReplyTo.

Building a live UI on top of these endpoints? Instead of polling them on an interval, subscribe to the Realtime WebSocket and refetch only when an event arrives.


List Conversations

GET /v1/phones/{phoneId}/conversations?page=1&limit=50

Returns conversations ordered by most recent message.

{
  "data": [
    {
      "id": "conv-uuid",
      "contactPhone": "5491155551234",
      "contactName": "John Doe",
      "contactId": "contact-uuid",
      "contactIdentifier": "5491155551234",
      "contactIdentifierType": "phone",
      "contactReplyTo": "5491155551234",
      "lastMessageAt": "2026-03-10T14:30:00.000Z",
      "lastInboundMessageAt": "2026-03-10T14:30:00.000Z",
      "serviceWindowExpiresAt": "2026-03-11T14:30:00.000Z",
      "isServiceWindowOpen": true,
      "createdAt": "2026-03-01T10:00:00.000Z"
    }
  ],
  "meta": { "pagination": { "page": 1, "limit": 50, "total": 12, "hasMore": false } }
}

The 24-Hour Service Window

Every conversation (list and detail) exposes the state of Meta's 24-hour customer service window, so you can decide whether to send a free-form message or a template before calling the send endpoint:

Field Type Description
lastInboundMessageAt string | null Timestamp of the contact's last inbound message. Null when the contact never wrote.
serviceWindowExpiresAt string | null When the window closes: lastInboundMessageAt + 24h. Null when the window never opened (only templates can be sent).
isServiceWindowOpen boolean Whether free-form messages can be sent right now, computed at response time.

Typical integration logic:

if (conversation.isServiceWindowOpen) {
  send free-form message (text, media, interactive, ...)
} else {
  send a template message
}

Caveats:


Get Messages

GET /v1/phones/{phoneId}/conversations/{conversationId}/messages?page=1&limit=50

Returns messages ordered by timestamp (newest first).

{
  "data": [
    {
      "id": "msg-uuid",
      "conversationId": "conv-uuid",
      "wamid": "wamid.HBgLNTQ5MTE1NTU...",
      "direction": "inbound",
      "type": "text",
      "content": "Hi there!",
      "status": "delivered",
      "timestamp": "2026-03-10T14:30:00.000Z",
      "editedAt": null,
      "mediaId": null,
      "mediaUrl": null,
      "mimeType": null,
      "filename": null,
      "caption": null,
      "reactions": [
        {
          "emoji": "👍",
          "reactor": "business",
          "direction": "outbound",
          "timestamp": "2026-03-10T14:31:00.000Z"
        }
      ],
      "createdAt": "2026-03-10T14:30:00.000Z"
    }
  ]
}

content is a plain string with the message's display text: the text body, the media caption, or the tapped button/list title — empty string when there is none. Type-specific data is exposed as flat top-level fields (see below).

This differs from webhook payloads, where data.content is an object ({ "text": ... }, { "mediaId": ... }). See the Webhooks guide for that shape.


Message Statuses

Messages go through these statuses:

Status Direction Description
pending Outbound Message queued for sending
sent Outbound Delivered to WhatsApp servers
delivered Outbound Delivered to the recipient's device
read Outbound Read by the recipient
failed Outbound Delivery failed (see error field)

Inbound messages are stored as delivered when received, and become read once you mark them read with the mark-as-read endpoint. Status updates only ever advance (a late, out-of-order webhook can never move a message backwards).


Message Fields by Type

Each message is returned flattened: content is the display text string, and type-specific data lives in dedicated top-level fields. Fields that do not apply to the message type are null.

Field group Fields Populated for
Core id, conversationId, wamid, direction, type, content, status, timestamp, editedAt, createdAt All messages
Media mediaId, mediaUrl, mimeType, filename, caption, transcript image, video, audio, document, sticker
Template templateName, templateLanguage, templateParameters template
Interactive interactiveType, buttons, interactiveHeader, interactiveBody, interactiveFooter, interactiveSections interactive
Quoted reply quotedMessageId, quotedMessageWamid, quotedMessageType, quotedMessagePreview, quotedMessageDirection Messages quoting a previous message
CTA URL ctaUrlBodyText, ctaUrlButtonText, ctaUrlUrl, ctaUrlHeaderText, ctaUrlHeaderImage, ctaUrlFooterText cta_url
Referral referral, referralSourceType, referralSourceId, referralHeadline, referralCtwaClid Inbound messages from Click-to-WhatsApp ads/posts
Reactions reactionEmoji, reactedToMessageId, reactions[] See Reactions

Example — inbound document message:

{
  "id": "msg-uuid",
  "conversationId": "conv-uuid",
  "wamid": "wamid.HBgLNTQ5MTE1NTU...",
  "direction": "inbound",
  "type": "document",
  "content": "Here is my payment receipt",
  "status": "delivered",
  "timestamp": "2026-03-10T14:30:00.000Z",
  "mediaId": "1234567890",
  "mimeType": "application/pdf",
  "filename": "receipt.pdf",
  "caption": "Here is my payment receipt",
  "reactions": []
}

To download inbound media from the messages API, use the mediaId with GET /v1/phones/{phoneId}/media/{mediaId}/download (file bytes) or GET /v1/phones/{phoneId}/media/{mediaId} (metadata). Both require Bearer auth. The pre-built mediaUrl/downloadUrl convenience links are only included in webhook payloads.

Reactions are not returned as standalone messages — they are aggregated onto their target message's reactions[] field (see below).


Reactions

Every message includes a reactions array aggregating all reactions applied to it (from the contact and/or the business):

"reactions": [
  { "emoji": "👍", "reactor": "5491155551234", "direction": "inbound", "timestamp": "2026-03-10T14:31:00.000Z" },
  { "emoji": "❤️", "reactor": "business", "direction": "outbound", "timestamp": "2026-03-10T14:32:00.000Z" }
]
Field Description
emoji The reaction emoji
reactor Who reacted: the contact identifier (for inbound) or "business" (for outbound)
direction inbound (from the contact) or outbound (from the business)
timestamp ISO 8601 time the reaction was applied

Each reactor has at most one active reaction per message: reacting again replaces it, and removing a reaction (empty emoji) deletes it. To apply a reaction, see Sending Messages.