Webhooks
The real-time push path — verify signatures and handle inbound media.
Webhooks push new events. Conversation endpoints let you pull
threads and history later. Use them for backfills, reconciliation, or agent
context. People see the same threads in Inbox. These endpoints need
conversations:read.
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);}curl "https://api.wabery.com/v1/conversations?contact_id=contact_...&active=true&limit=50" \ -H "Authorization: Bearer $WABERY_API_KEY"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 order — desc (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,});curl "https://api.wabery.com/v1/conversations/conv_.../messages?order=asc&limit=100" \ -H "Authorization: Bearer $WABERY_API_KEY"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", "provider": "instagram", "provider_media_id": null, "whatsapp_media_id": null, "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).
provider identifies whatsapp, instagram, or messenger.
whatsapp_media_id is retained for backward compatibility and is null on
Instagram/Messenger; use the channel-neutral provider_media_id, which can also
be null when Meta supplies only a temporary source URL.
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.
The refreshed URL is self-authorizing through its token query parameter. Do
not send the Wabery API key or an Authorization header when downloading it.
See Media messages for the normalized cross-channel
contract.
Webhooks
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.