Skip to main content
A conversation walks a small state machine. Knowing the states — and how transitions emit webhooks and system messages — is enough to keep an external inbox or CRM in sync without polling.

States

Pick resolved when you’ve answered the customer’s question; pick closed when the customer ghosted or the thread fizzled out. Both terminate the conversation, but resolved is the metric your CSAT report cares about.

Transition diagram

API cannot set bot_active. Once a human or API caller is in the loop, you cannot PATCH a conversation back to bot_active. Only the system places conversations into bot_active (on creation). The PATCH-allowed status enum is open | agent_requested | resolved | closed | archived.

Transitions

Trigger. The bot’s tool calls or the visitor explicitly asking (“speak to someone”). This transition is system-driven; it is also reachable from the API via PATCH status=agent_requested if you want to escalate based on your own logic.Side effects.
  • Inserts a system marker message visible to the visitor.
  • Emits a status_change row on conversation_events.
  • Fires conversation.updated.
Trigger. The first POST /conversations/{id}/messages (API) or the dashboard’s Take over button. Cannot be reached by PATCH directly — sending a reply is what claims the conversation.Side effects.
  • Inserts a “A team member has joined the conversation.” system marker.
  • Emits a human_takeover event row.
  • Fires message.created (marker), conversation.updated (with {from: "agent_requested", to: "open"}), then message.created (the agent reply).
Trigger. PATCH /conversations/{id} with {"status": "resolved"} or the dashboard’s Resolve button.Side effects.
  • Stamps resolved_at.
  • Inserts a “This conversation has been resolved.” system marker.
  • Emits a status_change event row.
  • Fires message.created (marker), conversation.updated.
  • Schedules the end-of-conversation summary job that stamps summary and sentiment_score.
Trigger. PATCH /conversations/{id} with {"status": "closed"}. Use when the thread is done but not necessarily solved (visitor ghosted, etc).Side effects.
  • Stamps closed_at.
  • Inserts a “This conversation has been closed.” system marker.
  • Emits a status_change event row.
  • Fires message.created (marker), conversation.updated.
  • Schedules the end-of-conversation summary job.
Trigger. Inactivity timer (default 7 days after resolved_at). System-driven; no equivalent API call.Side effects.
  • Stamps closed_at.
  • No system marker — the resolved state already announced the end of the thread.
  • Fires conversation.updated.
Trigger. PATCH /conversations/{id} with {"status": "archived"}. Hides the conversation from the default inbox.Side effects.
  • Stamps archived_at.
  • No system marker (hidden from visitor view anyway).
  • Fires conversation.updated.
Trigger. PATCH /conversations/{id} with {"status": "open"}. The dashboard requires contact_email (to email a resume link), but the API does not — you can reopen any conversation programmatically.Side effects.
  • Clears resolved_at / closed_at.
  • Emits a status_change event row.
  • Fires conversation.updated.

Webhook events per transition

Every PATCH /conversations/{id} that changes any field — not just status — fires conversation.updated. The webhook payload includes a changes object with {from, to} for every modified field.

System marker messages

Several transitions insert a system-role message into the thread so the visitor sees what happened. These show up in GET /conversations/{id}/messages with role: "system" and metadata.event set to one of: Filter these out client-side if your UI surfaces them differently than visitor/agent messages.