Conversations

List and inspect conversations, send messages to customers, and take a conversation over from the bot.

List Conversations

Returns a paginated list of all conversations for a given chatbot.

GET /api/dev/v1/chatbots/{chatbot_id}/conversations
Authorization: Bearer <api-key>

Filter Parameters

ParameterTypeDescription
statusstringFilter by conversation status. One of: ACTIVE, CLOSED, ARCHIVED, HANDOFF_PENDING, AGENT_ACTIVE.
channelstringFilter by channel (e.g. whatsapp, web, sms, api).
start_timestringFilter conversations created at or after this time (ISO 8601, e.g. 2026-01-01T00:00:00Z).
end_timestringFilter conversations created at or before this time (ISO 8601).

Pagination Parameters

ParameterTypeDefaultDescription
limitinteger50Page size. Min 1, max 67.
cursorstring-Pagination cursor from a previous response.
curl -X GET "https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/conversations?status=ACTIVE&channel=whatsapp&limit=25" \
  -H "Authorization: Bearer <your-api-key>"

Response - 200 OK

{
  "count": 1,
  "conversations": [
    {
      "id": "01JPQ...",
      "chatbot_id": "01JMXYZ...",
      "user_phone": "+255712345678",
      "contact_name": "Lorem Ipsum",
      "channel": "whatsapp",
      "status": "ACTIVE",
      "last_message_id": "01JRS...",
      "last_message_at": "2026-02-18T14:35:00Z",
      "last_message_preview": "Thanks for your help!",
      "unread_count": 2,
      "created_at": "2026-02-18T14:22:00Z",
      "updated_at": "2026-02-18T14:35:00Z"
    }
  ],
  "next_cursor": "eyJpZCI6IjAxSlBRIn0="
}

Get Conversation

Retrieves a single conversation by ID.

GET /api/dev/v1/chatbots/{chatbot_id}/conversations/{conversation_id}
Authorization: Bearer <api-key>
curl -X GET https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/conversations/<conversation-id> \
  -H "Authorization: Bearer <your-api-key>"

Response - 200 OK - returns a single ConversationRecord (same shape as the list items above).


Get Conversation Messages

Returns messages for a conversation, ordered oldest first.

GET /api/dev/v1/chatbots/{chatbot_id}/conversations/{conversation_id}/messages
Authorization: Bearer <api-key>

Pagination Parameters

ParameterTypeDefaultDescription
limitinteger50Page size. Min 1, max 67.
cursorstring-Pagination cursor from a previous response.
curl -X GET "https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/conversations/<conversation-id>/messages?limit=25" \
  -H "Authorization: Bearer <your-api-key>"

Response - 200 OK

The wrapper carries count, total, page, limit, pages, next_cursor and messages. Each entry in messages is one stored turn. The response.messages array holds the same shapes as the Chat API send-message response.

{
  "count": 1,
  "total": 1,
  "page": 1,
  "limit": 25,
  "pages": 1,
  "next_cursor": null,
  "messages": [
    {
      "id": "01K5M2Q8Z7C3V9R1T4Y6W0B2NF",
      "conversation_id": "01K5M2Q8Z7C3V9R1T4Y6W0B2NE",
      "message": { "text": "hi" },
      "response": {
        "messages": [
          { "type": "TEXT", "text": "Lorem ipsum dolor sit amet, welcome!" },
          {
            "type": "BUTTONS",
            "text": "Choose an option.",
            "buttons": [
              { "id": "view_products", "title": "Products" },
              { "id": "support_menu", "title": "Support" }
            ]
          }
        ]
      },
      "message_type": "text",
      "responder": "BOT",
      "responder_name": null,
      "received_at": "2026-09-22T10:40:01+00:00",
      "responded_at": "2026-09-22T10:40:02+00:00",
      "status": "delivered",
      "created_at": "2026-09-22T10:40:01+00:00",
      "media": null
    }
  ]
}

Send a Message to a Customer

Sends a message into an existing or new conversation on WhatsApp, Messenger or the web chat, outside of a chat session. Use it to answer from your own system, to follow up after an order, or to re-open a conversation with a WhatsApp template.

POST /api/dev/v1/chatbots/{chatbot_id}/messages
Authorization: Bearer <api-key>
X-Sarufi-Signature: t=<unix-seconds>,v1=<hmac-sha256>
Content-Type: application/json

Every send is signed

Besides the API key, each request carries a signature made with the chatbot's webhook_secret (see Chatbots). Sign the exact bytes you send: v1 is the hex HMAC-SHA256 of "<t>.<raw body>", where t is the current Unix time in seconds. A signature older than five minutes is refused.

Request Body

FieldTypeRequiredDescription
channelstringyesWHATSAPP, MESSENGER or WEB.
tostringyesWho receives it. WhatsApp: the phone number in international format. Messenger: the page-scoped user id. Web: the conversation_id of the visitor's chat.
messageobjectyesOne message, chosen by its type. See the shapes below.
metadataobjectnoAnything you want stored with the message, for example your own order id.
sender_namestringnoThe person replying, when it is a person. Trimmed and cut at 60 characters. Without it the message is signed with your API key's name.

Message shapes for sending

typeFields
textbody
buttonsbody, buttons (up to 3, each a string or { id, title }), optional header, footer
listbody, button_text, items (each { id, title, description?, section? }), optional header, footer
image, video, audio, documenturl, optional caption, filename for a document
cta_urlbody, display_text, url
templatename, language (default en), optional parameters. WhatsApp only

On WhatsApp a plain message is delivered only within 24 hours of the customer's last message. Outside that window send a template; a plain send is refused with window_closed.

BODY='{"channel":"WHATSAPP","to":"+255712345678","message":{"type":"text","body":"Your order has shipped."}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "<webhook-secret>" | awk '{print $NF}')

curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/messages \
  -H "Authorization: Bearer <your-api-key>" \
  -H "X-Sarufi-Signature: t=$T,v1=$SIG" \
  -H "Content-Type: application/json" \
  --data "$BODY"
import hashlib, hmac, json, time, requests

payload = {
    "channel": "WHATSAPP",
    "to": "+255712345678",
    "message": {"type": "text", "body": "Your order has shipped."},
}
body = json.dumps(payload, separators=(",", ":"))
t = int(time.time())
sig = hmac.new(b"<webhook-secret>", f"{t}.{body}".encode(), hashlib.sha256).hexdigest()

response = requests.post(
    "https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/messages",
    data=body,
    headers={
        "Authorization": "Bearer <your-api-key>",
        "X-Sarufi-Signature": f"t={t},v1={sig}",
        "Content-Type": "application/json",
    },
)
print(response.json())

Response - 201 Created

{
  "message_id": "wamid.HBgLM...",
  "status": "sent",
  "channel": "WHATSAPP",
  "conversation_id": "01JPQ...",
  "responder": "HUMAN",
  "held": true
}
FieldTypeDescription
message_idstring | nullThe id the channel gave the message. null for web.
statusstringsent, or queued when a web visitor is away: the message waits up to seven days and is shown when they return.
channelstringThe channel it went out on.
conversation_idstringThe conversation it was filed in. Poll its messages to read the customer's replies.
recipient_onlinebooleanWeb only. Whether the visitor was there to read it.
responderstringHow the message was filed: HUMAN, under your API key's name.
heldbooleantrue when this send put you in charge of the conversation. See Who is answering.

Errors

StatusWhen
401The signature header is missing or does not match the body.
400The channel is not connected to this chatbot.
422The message is malformed, or window_closed on WhatsApp.
404Web: no conversation with that id belongs to this chatbot.
410Web: the visitor has been away for more than seven days.
502The channel refused to deliver it.

Who Is Answering

A message sent through this endpoint is treated like a person stepping in from the inbox, not as something the bot said.

  • It is filed as a HUMAN message, signed with sender_name or, without one, your API key's name as responder_name. A web visitor sees a text reply under that name, as they would an inbox agent's.
  • If the bot was handling the conversation, your send takes it over: the conversation moves to AGENT_ACTIVE, held is true, and the bot stays quiet.
  • You keep the conversation for 15 minutes after your last send or action. Every send extends it. When you go quiet, the customer's next message is answered by the bot again.
  • A conversation an inbox agent already holds is not taken: your message is still delivered, held is false, and the agent keeps it.
  • Read what the customer writes back by polling Get Conversation Messages.

To hold a conversation before you have anything to say, or to hand it back the moment you are done, use the action below.


Hold or Release a Conversation

POST /api/dev/v1/chatbots/{chatbot_id}/conversations/{conversation_id}/action
Authorization: Bearer <api-key>
Content-Type: application/json
FieldTypeRequiredDescription
actionstringyestakeover to hold the conversation so the bot stays quiet, or release to hand it back to the bot at once.
curl -X POST https://developers.sarufi.io/api/dev/v1/chatbots/<chatbot-id>/conversations/<conversation-id>/action \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"action":"release"}'

Response - 200 OK

{ "status": "released" }

takeover answers { "status": "held" }. A takeover on a conversation an inbox agent holds, or one that is closed, is refused with 409. A conversation id that is not this chatbot's answers 404.


Conversation Object

FieldTypeDescription
idstringConversation ULID.
chatbot_idstringParent chatbot ID.
user_phonestringUser phone number (e.g. +255712345678 for WhatsApp).
contact_namestring | nullContact display name, if available.
channelstringIntegration channel: whatsapp, web, sms, api.
statusstringConversation status: ACTIVE, CLOSED, ARCHIVED, HANDOFF_PENDING, AGENT_ACTIVE.
last_message_idstring | nullID of the last message in the conversation.
last_message_atstringTimestamp of the most recent message.
last_message_previewstring | nullPreview text of the last message.
unread_countintegerNumber of unread messages.
created_atstringWhen the conversation was created.
updated_atstring | nullWhen the conversation was last updated.

Message Object

FieldTypeDescription
idstringULID of the turn.
conversation_idstringParent conversation ID.
messageobject | nullWhat the user sent.
responseobject | nullWhat the bot replied. Its messages array holds the same shapes as the Chat API send-message response.
message_typestringType of the user's message.
responderstringWho responded: BOT or HUMAN.
responder_namestring | nullFor a HUMAN reply, who it was: the inbox agent's name, or the API key's name for a message sent through this API.
received_atstringISO 8601. When the user message was received.
responded_atstring | nullISO 8601. When the response was sent.
statusstringMessage status. Includes the WhatsApp delivery tick once one arrives.
created_atstringISO 8601. When the record was created.
mediaobject | nullMedia the user sent: type, url, mime_type, caption, filename, transcript. Never describes bot messages.

Older history rows

Rows recorded before 2026-09-22 may contain combined bot messages. See History of combined messages for how they are returned.

Want to chat with your bot via API?

Use the Chat API to create sessions and send messages programmatically. Conversations created via the Chat API appear here with channel=api.

On this page