Chat handover (human takeover)
Hand a live chat session from the agent to a human operator, in the same conversation the user is already in. The agent goes quiet, the user sees a short placeholder, an operator types directly into the chat from the admin, and on release the agent resumes — with the operator's messages in its context.
This is a complement to Human-in-the-loop workflows and escalate_to_human, not a replacement. Those pause a workflow or file an offline case; handover rescues a live conversation in place — a customer-facing chat that hits an edge case, a sales conversation that needs a person past a threshold, a regulated flow that legally requires a human.
Web chat only
Handover currently covers web chat sessions. Taking over SMS / WhatsApp conversations is planned for a later release.
How it works
- The agent calls the
request_handovertool (or an operator barges in from the admin). The session entersrequested. - The user's chat keeps streaming, but instead of an agent answer it shows your configured placeholder ("A team member will respond shortly.").
- Martha notifies your configured channels and emits a
chat.session.handover_requestedevent. - An operator opens the session in the admin, clicks Take over (
requested→active), and types. Their messages appear in the user's chat live. - The operator clicks Release. The session returns to
noneand the next user message routes back to the agent, which now sees the human exchange in its history.
If nobody picks up, or an operator claims and then goes idle, the session auto-releases after a configurable timeout so the conversation is never stuck.
Enable it for a tenant
Handover is off by default. Configure it with the settings endpoint:
PUT /api/admin/handover/settings
{
"enabled": true,
"fallback_message": "A team member will respond shortly.",
"notify_channel_ids": ["<notification-connection-id>"],
"timeout_minutes": 15
}| Field | Meaning |
|---|---|
enabled | Master switch for the tenant. When off, sessions can never enter handover. |
fallback_message | What the user sees while waiting for / talking to an operator. |
notify_channel_ids | Notification connection IDs to ping when a handover is requested (see below). |
timeout_minutes | Operator-inactivity auto-release window. Default 15; 0 disables auto-release. |
GET /api/admin/handover/settings returns the current policy.
Get operators notified
notify_channel_ids are the IDs of notification channel connections. When a handover is requested, Martha sends a message to each — with the session reason and a deep link to the operator seat — so operators do not have to watch the sessions tab.
Use Slack or an authenticated webhook
Notification delivery resolves a stored credential, so the connection must carry one: a Slack connection (the webhook URL is the secret) or a webhook connection configured with an auth value. A no-auth webhook has no stored credential and will not deliver.
You can also react to the lifecycle events yourself — see Custom escalation.
Operate a handover (admin)
In the admin Sessions area:
- The session list shows a state badge (Handover requested / active) and a filter by state, and the Sessions nav shows a count of pending handovers.
- Open a session to reach the operator seat: Take over / Claim, a composer to type into the conversation, and Release. The banner shows the reason the agent (or workflow) gave.
- The operator's name is resolved from the identity provider — the seat shows a person's name, not an internal id.
Who may operate a chat is controlled by the chat_session / claim grant, managed through the standard access surfaces (the same grant model as approvals). A session operator does not need to be a full tenant admin.
Let the agent ask for a human
Grant the request_handover tool to the agent (or API client) that runs the chat, the same way you grant any other tool. Then the agent can hand off on its own when it should not proceed — a request beyond its authority, a sensitive or regulated situation, or the user explicitly asking for a person. If handover is not enabled for the tenant, the tool returns a graceful error and the agent falls back to answering or to escalate_to_human.
Per-client control
Whether a client's sessions may enter handover at all is a per-client setting (Clients → edit → Human handover):
| Setting | Behavior |
|---|---|
| Auto (default) | Allowed for internal clients; blocked for public embed clients. |
| Allowed | Handover permitted for this client. |
| Blocked | Handover refused for this client. |
The Auto default means a real person never appears unexpectedly inside a public embedded chat widget unless you deliberately allow it.
Auto-release
An active handover with no operator activity for timeout_minutes is released automatically, and the agent resumes with the full conversation. The clock resets on every operator message, so an actively-typing operator is never cut off. Set timeout_minutes to 0 to disable auto-release entirely.
SMS and WhatsApp sessions
Handover works on messaging channels, not just the web chat. When an SMS or WhatsApp session enters handover, the same operator seat applies — with two differences that follow from the channel:
- One placeholder, not one per message. When the handover starts, the tenant's
fallback_messageis texted to the user once. Further messages the user sends while waiting are received and shown to the operator live, but the user is not re-texted each time (unlike the web widget, where every message gets an ack) — SMS is billed per segment. - The agent stops texting. While a handover is requested or active, the agent neither runs nor sends anything on that channel. If an operator takes over while the agent is mid-reply, that in-flight reply is suppressed too.
In the operator seat, the composer shows the destination channel and phone number so it is clear the reply goes out over SMS or WhatsApp — from the session's own business number, the one the user has been messaging. Replies are delivered through your messaging provider; if a delivery fails the operator sees an inline notice (the message is still recorded in the conversation).
WhatsApp 24-hour window. WhatsApp only allows free-form business messages within 24 hours of the user's last message. During a live handover that window is normally open. If an operator replies after it has closed, delivery falls back to a pre-approved reopen template. Configure it in handover settings:
| Setting | Meaning |
|---|---|
whatsapp_reopen_template_name | Name of a template pre-approved in your WhatsApp provider account, with a single body placeholder that carries the operator's text. |
whatsapp_reopen_template_language | Template language code (default en). |
If no reopen template is configured and the window has closed, the operator's message is recorded but delivery fails with a clear notice — no template name is guessed, because the provider requires per-account pre-approval.
Settings are managed today through the handover settings API; a unified settings form covering the web and messaging options together is tracked as a follow-up (#977), sequenced after this so one form covers both.
Custom escalation
Every transition emits an event on the tenant event bus:
| Event | When |
|---|---|
chat.session.handover_requested | A handover is requested (by the agent, an operator, or a workflow). |
chat.session.handover_claimed | An operator takes the session. |
chat.session.handover_released | The session is released — by the operator or by inactivity auto-release (data.reason distinguishes them). |
The built-in notify_channel_ids fan-out is the out-of-the-box ping. For richer policy — urgency-based routing, on-call paging, a CRM update on release — wire an event-driven trigger to a workflow that reacts to chat.session.handover_requested. The admin takeover and your escalation workflow never call each other; they meet only on these events.
Related
- Human-in-the-loop workflows — pausing a workflow for approval or an external event.
- Notification channels — the connections
notify_channel_idspoint at. - Event-driven triggers — react to the handover lifecycle events.
- Embeddable chat — where the per-client handover default matters most.