Skip to content

Conversations

A conversation is the thread between your agent and one lead. The API gives you the inbox (leads with message activity) and the transcript of each lead, read-only.

Method Path Scope
GET /conversations conversations:read
GET /conversations/{leadId}/messages conversations:read

The inbox: every lead that exchanged at least one message, most recent activity first.

Terminal window
curl "https://app.whatsetter.com/api/v1/conversations?campaign_id=acme-france-1-ab12&limit=25" \
-H "Authorization: Bearer ws_live_…"
{
"data": [
{
"lead_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"phone": "33612345678",
"whatsapp_id": "33612345678@c.us",
"name": "Julien",
"status": "in_progress",
"qualified": false,
"replied": true,
"campaign_id": "acme-france-1-ab12",
"messages_count": 6,
"last_message_at": "2026-09-18T16:42:05.000Z",
"last_message_direction": "inbound"
}
],
"pagination": { "has_more": false, "next_cursor": "66f1a2b3c4d5e6f7a8b9c0d1" }
}

campaign_id accepts the campaign’s slug or its id. Without it, conversations of group-purpose campaigns stay out, same as the dashboard. last_message_direction: "inbound" means the lead spoke last: that is your “needs an answer” signal.

leadId is the lead’s id from /leads or /conversations. Messages come back newest first.

Terminal window
curl "https://app.whatsetter.com/api/v1/conversations/66f1a2b3c4d5e6f7a8b9c0d1/messages?limit=50" \
-H "Authorization: Bearer ws_live_…"
{
"data": [
{
"id": "66f1a2b3c4d5e6f7a8b9c0e6",
"direction": "inbound",
"text": "Yes, 2pm works for me.",
"type": "USER_MESSAGE",
"has_media": false,
"sent_at": "2026-09-18T16:42:05.000Z"
},
{
"id": "66f1a2b3c4d5e6f7a8b9c0e5",
"direction": "outbound",
"text": "Are we still on for tomorrow at 2pm?",
"type": "MANUAL_RESPONSE",
"has_media": false,
"sent_at": "2026-09-18T16:40:10.000Z"
}
],
"pagination": { "has_more": false, "next_cursor": "66f1a2b3c4d5e6f7a8b9c0e5" }
}
Field Meaning
direction inbound when the lead wrote, outbound when the agent, a teammate or the API wrote.
text The message text, or null for a media-only message.
type Internal interaction type, for example USER_MESSAGE, AI_RESPONSE, LEAD_WELCOME, FOLLOWUP, MANUAL_RESPONSE. Treat unknown values as opaque.
has_media true when the message carried an image, voice note or document. The media itself is never linked.
sent_at ISO 8601, UTC.

Both endpoints return pagination.has_more and pagination.next_cursor. Pass cursor=<next_cursor> to get the next page; limit is 1 to 100, 25 by default. A cursor that points to a deleted row answers 422 validation_error with “Invalid or expired cursor”: restart from the first page.

To sync an inbox, poll GET /conversations and only fetch the transcript of leads whose last_message_at moved. For real-time, prefer webhooks.

Transcripts contain phone numbers, names and whatever the person wrote. Treat them as personal data: store only what your use case needs, respect your retention policy, and never expose them to people who could not open the WhatSetter inbox. phone is digits only, without the +.

HTTP code When
401 missing_api_key, invalid_api_key Key absent, unknown or revoked.
403 insufficient_scope The key lacks conversations:read.
404 not_found Unknown lead, or a lead of another workspace.
422 validation_error limit out of 1 to 100, invalid or expired cursor.
429 rate_limited Too many requests this minute. Honor Retry-After.