States
Transition diagram
Transitions
bot_active → agent_requested
bot_active → agent_requested
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_changerow onconversation_events. - Fires
conversation.updated.
agent_requested → open
agent_requested → open
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_takeoverevent row. - Fires
message.created(marker),conversation.updated(with{from: "agent_requested", to: "open"}), thenmessage.created(the agent reply).
open → resolved
open → resolved
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_changeevent row. - Fires
message.created(marker),conversation.updated. - Schedules the end-of-conversation summary job that stamps
summaryandsentiment_score.
open → closed
open → closed
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_changeevent row. - Fires
message.created(marker),conversation.updated. - Schedules the end-of-conversation summary job.
resolved → closed (automatic)
resolved → closed (automatic)
Trigger. Inactivity timer (default 7 days after
resolved_at). System-driven; no equivalent API call.Side effects.- Stamps
closed_at. - No system marker — the
resolvedstate already announced the end of the thread. - Fires
conversation.updated.
any → archived
any → archived
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.
Reopening (resolved | closed | archived → open)
Reopening (resolved | closed | archived → open)
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_changeevent 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 inGET /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.

