Skip to content
Start free

Conversations & history

Webhooks push each event to you in real time. Conversations let you pull: list threads, page back through message history, and re-fetch inbound media after the webhook’s signed URL has expired. Use them to backfill after downtime, reconcile against your own store, or power an agent that needs prior context. All endpoints require the conversations:read scope.

Cursor-paginated. Filter by contactId, or by active to get only threads inside their messaging window.

const { data, has_more } = await wabery.conversations.list({
contactId: "contact_...",
active: true,
limit: 50,
});
// Or iterate every page without managing the cursor:
for await (const convo of wabery.conversations.listAll({ active: true })) {
console.log(convo.id, convo.last_message_at);
}

A conversation:

{
"object": "conversation",
"id": "conv_...",
"contact_id": "contact_...",
"phone": "+14155550100",
"last_channel_id": "channel_...",
"is_active": true,
"started_at": "2026-06-20T14:00:00Z",
"last_message_at": "2026-06-20T14:21:10Z"
}

is_active reflects whether the 24-hour window is currently open. Reply with messages.send({ conversationId: "conv_...", ... }); outside the window use an approved template.

listMessages is cursor-paginated and ordered by orderdesc (newest first, the default) for a live view, asc to replay a thread from the start.

const { data } = await wabery.conversations.listMessages("conv_...", {
order: "asc",
limit: 100,
});

A message:

{
"object": "message",
"id": "msg_...",
"conversation_id": "conv_...",
"contact_id": "contact_...",
"channel_id": "channel_...",
"direction": "inbound",
"type": "image",
"status": "received",
"content": null,
"media": {
"type": "image",
"file_name": "msg_...-image.jpg",
"file_size": 248913,
"mime_type": "image/jpeg",
"whatsapp_media_id": "wamid...",
"url": "https://storage.wabery.com/assets/inbound/asset_...?token=...",
"expires_at": "2026-06-27T14:21:10Z",
"status": "available",
"accessible": true
},
"timestamp": "2026-06-20T14:21:10Z"
}

direction is inbound or outbound. For outbound messages that failed, failure carries the provider error (provider, code, message, fbtrace_id).

The media url in a message.received webhook is a short-lived signed URL (~1 hour). Reading the same message through history re-signs a fresh URL as long as the retained object still exists — its lifetime is bounded by expires_at (24h on the free plan, 7 days on paid). Check before downloading:

const { data } = await wabery.conversations.listMessages("conv_...");
for (const message of data) {
const media = message.media;
if (media?.status !== "available" || !media.url) continue; // expired / not cached
const response = await fetch(media.url);
await storeFile({ messageId: message.id, body: await response.arrayBuffer() });
}

If retention has lapsed, status is expired and url is null — the object is gone and cannot be re-signed. Store files on your side promptly.

Webhooks & events

The real-time push path — verify signatures and handle inbound media.

Sending messages

Reply inside the window with text, media, or interactive messages.

Errors & rate limits

Cursor pagination, rate limits, and idempotency.