Reading conversations

Reading conversations

Read your WhatsApp history over HTTP: list threads (one per contact) and page through the messages in a thread. Use this to backfill, build your own inbox, or reconcile against the webhooks that push messages to you in real time.

Both endpoints need the whatsapp scope on your app key and work on native channels.


List threads

GET /channels/{id}/threads returns the channel’s conversations, newest activity first, paged with limit and offset.

curl "https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/threads?limit=20&offset=0" \
  -H "Authorization: Bearer vai_app_…"
{ "success": true, "data": {
  "threads": [
    {
      "id": "a17c9e0b-…",
      "contactPhone": "+919876543210",
      "contactName": "Asha Rao",
      "lastMessageAt": "2026-07-18T09:14:22Z",
      "lastInboundAt": "2026-07-18T09:14:22Z",
      "lastBody": "Where is my order?",
      "lastFromMe": false,
      "unreadCount": 2,
      "aiPaused": false,
      "createdAt": "2026-07-01T11:02:10Z"
    }
  ],
  "total": 137,
  "limit": 20,
  "offset": 0,
  "hasMore": true
} }
Query paramNotes
limitPage size — optional, default 50, maximum 200 (larger values are clamped to 200)
offsetRows to skip — page with offset += limit while hasMore is true

Thread fields:

FieldNotes
idThread id — use it in /threads/{id}/messages
contactPhoneThe customer’s number (E.164)
contactNameWhatsApp profile name, if known
lastMessageAtTimestamp of the most recent message (in or out)
lastInboundAtTimestamp of the last inbound message — the anchor for the 24-hour window
lastBodyPreview text of the last message
lastFromMetrue if the last message was outbound
unreadCountUnread inbound messages on this thread
aiPausedtrue if AI auto-reply is paused for human takeover on this thread
createdAtWhen the thread was first created

Page until hasMore is false. total is the full thread count for the channel.


Read messages in a thread

GET /threads/{id}/messages returns messages newest-first, paged backwards with a cursor (beforeId), not an offset.

curl "https://api.telenow.ai/api/app-whatsapp/threads/a17c9e0b-…/messages?limit=30" \
  -H "Authorization: Bearer vai_app_…"
{ "success": true, "data": {
  "threadId": "a17c9e0b-…",
  "contactPhone": "+919876543210",
  "messages": [
    {
      "id": "e5b2…",
      "direction": "in",
      "wamid": "wamid.HBgLMTQxNTU1NTAxMDAVAgAREg…",
      "from": "+919876543210",
      "body": "Here's the receipt you asked for",
      "type": "image",
      "sentBy": null,
      "status": "delivered",
      "content": { "media": { "id": "9f8e7d…", "mime": "image/jpeg", "caption": "receipt" } },
      "media": { "mime": "image/jpeg", "filename": "receipt.jpg", "url": "https://…/media/9f8e7d…" },
      "createdAt": "2026-07-18T09:14:22Z"
    },
    {
      "id": "e5b1…",
      "direction": "out",
      "wamid": "wamid.HBgLMTQxNTU1NTAxMDAVBgAREg…",
      "from": "+14155550100",
      "body": "Sure — could you send the receipt?",
      "type": "text",
      "sentBy": "agent",
      "status": "read",
      "content": null,
      "media": null,
      "createdAt": "2026-07-18T09:13:05Z"
    }
  ],
  "limit": 30,
  "hasMore": true
} }
Query paramNotes
limitPage size — optional, default 50, maximum 200 (larger values are clamped to 200)
beforeIdCursor: the oldest message id you hold; returns messages strictly older than it

Message fields:

FieldNotes
idMessage id — the cursor for beforeId
directionin (from the customer) or out (from you)
wamidWhatsApp message id — correlates with whatsapp.message.status events
fromSender’s number (E.164)
bodyText body / caption
typetext, image, audio, video, document, sticker, location, interactive, reaction, contacts
sentByFor outbound: who sent it (e.g. agent, a user id); null for inbound
statussent | delivered | read | failed (outbound)
contentStructured extract for non-text types (media / location / reply / button / reaction / contacts), else null
media{ mime, filename, url } when the message has an attachment, else null
createdAtTimestamp

Paging backwards with beforeId

To load older messages, pass the id of the oldest message you have as beforeId — the endpoint walks backwards from there. Repeat while hasMore is true:

# Next (older) page: beforeId = the last message id from the previous page
curl "https://api.telenow.ai/api/app-whatsapp/threads/a17c9e0b-…/messages?limit=30&beforeId=e5b1…" \
  -H "Authorization: Bearer vai_app_…"
{ "success": true, "data": {
  "threadId": "a17c9e0b-…",
  "contactPhone": "+919876543210",
  "messages": [ /* the 30 messages immediately older than e5b1… */ ],
  "limit": 30,
  "hasMore": false
} }

When hasMore is false, you’ve reached the start of the thread.

Downloading media

When media is present, media.url points at the attachment. It resolves through GET /api/app-whatsapp/media/{id} (served as a nosniff attachment). See Sending & receiving media for the full media flow.


Reading does not clear unread

Reading a thread via the API does not clear its unreadCount and does not send read receipts to the customer. Only opening the thread in the dashboard — or explicitly calling POST /messages/read — marks messages read (blue ticks) and resets the badge.

So a backend that syncs history through these endpoints won’t accidentally “read” a customer’s messages out from under your support team. When your bot has actually handled a message and you want the customer to see blue ticks, call /messages/read deliberately — see Typing indicators & read receipts.


Endpoint reference

Base /api/app-whatsapp, scope whatsapp, native channels only.

MethodPathPurpose
GET/channels/{id}/threads?limit&offsetConversations, paged → { threads, total, limit, offset, hasMore } (limit default 50, max 200)
GET/threads/{id}/messages?limit&beforeIdMessage history, cursor-paged → { threadId, contactPhone, messages, limit, hasMore } (limit default 50, max 200)

The dashboard equivalents are GET /api/orgs/{orgId}/whatsapp-hub/channels/{id}/threads and /threads/{tid}/messages with a user JWT.