Human handoff
Sooner or later a visitor needs a person. This page covers how your assistant makes that happen — by sending the request to a support inbox, or by pushing it into a system you run — and how to configure it.
Everything here lives in the Human Handoff card on the Channels tab of the assistant editor. Expand the card to configure it. Its header counts the escalation channels you have configured — only the ones the assistant is actually served, so the count never claims a hand-off route the AI was never given.
How a conversation reaches a person
Your assistant does not guess. Turn on at least one escalation channel and it is given an
escalate_to_human tool, which it calls when a visitor asks for a human, when it cannot resolve
the issue, or when the subject is billing, account access, a complaint, or something similarly
sensitive. The channel Description you write is how the model decides which of your channels
fits the situation.
Handoff works the same way in text chat and in voice conversations.
Your team answers an escalation later, so before one is sent the assistant has to have a way to reach the visitor back. If it has neither an email address nor a phone number for them, the escalation is rejected and the assistant is told to collect the details and try again. That check runs on the server, so it holds regardless of what the browser sends. The one exception is when the visitor was asked and explicitly declined; the assistant can then send the request anyway rather than lose it.
Escalation channels
Add a channel with Add channel, or + Add Channel once you have at least one. You can save up to five per assistant.
Every channel has the same four controls, whatever its type:
| Control | Notes |
|---|---|
| Channel Type | Email or Webhook. Changing the type clears the type-specific fields |
| Enabled | Off keeps the channel configured but hides it from the assistant. The badge reads Active or Disabled |
| Display Name | What the visitor sees on the button. Up to 100 characters |
| Description | Up to 500 characters. This is written for the assistant, not the visitor — it is how the model decides which channel fits the situation, so be specific ("For urgent billing issues") |
Remove Channel at the bottom of a channel deletes it. Changes take effect when you Save the assistant.
Note
If an assistant carries a channel whose type this deployment does not serve, the card badges it Unavailable instead of Active, and the assistant is never offered it. Change its type or remove it.
Display mode
Both types have a Display Mode setting:
- Inline in Chat — the action happens in the conversation itself.
- Open in Popup Window — Hiroi opens a small standalone window with a single call-to-action button. If the browser blocks the popup, the widget falls back to the inline behaviour.
Leave Webhook on Inline in Chat. A webhook has nothing for a visitor to click, so popup mode gives them an empty window — and the request is not delivered.
| Field | Notes |
|---|---|
| Support Email | Where the escalation is sent |
| CC (optional) | A second address, copied on every escalation |
| Include conversation transcript | On by default. Attaches the last 20 messages of the conversation to the email |
In Inline in Chat mode Hiroi sends this email itself, so there is no button for the visitor to click. The message carries the urgency the assistant assigned, its summary of what the visitor wants, the issue, and the visitor's name and contact details. The visitor sees a confirmation in the chat. If sending fails, no confirmation is shown — a retry button appears instead.
In Open in Popup Window mode the visitor gets a Send Email button that opens their own mail client instead — Hiroi does not send the email.
Webhook
Use this to push escalations into your own system — a ticket queue, an on-call pager, a Slack relay you host.
| Field | Notes |
|---|---|
| Webhook URL | Your endpoint. http:// and https:// only |
| HTTP Method | POST, PUT, or PATCH |
Hiroi sends a JSON body:
{
"event": "escalation",
"site_id": "...",
"site_name": "Acme Support",
"visitor": { "name": "Dana Reed", "email": "dana@example.com" },
"issue": {
"reason": "Card declined on renewal",
"summary": "Wants the renewal retried on a new card before Friday",
"urgency": "high"
},
"transcript": [ { "role": "user", "content": "..." } ]
}
transcript is included whenever the widget sends one — webhook channels have no transcript
toggle in the editor, so it is always included. It is capped at the last 20 messages, each
truncated to 500 characters. urgency is low, medium, or high.
Requests time out after 10 seconds, redirects are not followed, and URLs that resolve to a private address are refused. Any response status below 400 counts as delivered; anything else shows the visitor a retry button. This delivery is not signed — if you need a signature and a delivery log, use an Action instead.
Events for Actions
If you are wiring the hand-off into your own tooling, escalation.triggered is available to
Actions. It fires when the assistant uses an escalation channel, carrying
the channel type, the reason, the urgency and the visitor's name and email — and it fires before
delivery is attempted, so subscribe to it if you want your own alerting independent of whether the
channel itself succeeded.