An AI support agent is only safe to run if a customer can reach a person when the AI is the wrong tool. In LayBuild, a handoff happens for a small number of explicit reasons, it flips the conversation to a HANDOFF status, stops the AI from replying, and alerts your team. It does not summarize the chat and it does not try to read the customer's mood. Below is what happens step by step, where we think the design is still thin, and how to set up escalation rules you can defend.
What triggers a handoff
There are four paths into a handoff, and they behave slightly differently.
The first is an explicit request in the web widget or API. Before retrieval runs, every customer message is checked against a pattern that looks for phrases such as "talk to a human", "speak to an agent", "real person", "escalate" or "customer service". A match marks the turn as a handoff request. Whether the handoff actually happens depends on an organization setting, ALLOW_HUMAN_HANDOFF, which is on by default. An admin can switch it off from the admin settings page. With it off, a customer who asks for a person keeps getting AI replies.
The second is the model itself. The built-in system prompts tell the model that when a customer asks for a human, or the issue needs an action it cannot take (refund approval, account changes, disputes, legal or safety concerns), it should reply with one fixed sentence: "I'm connecting you with a human support specialist right now." While the reply streams, a watcher looks for that sentence and, when it appears, stops generation and marks the turn as a handoff request. This path is gated by the same ALLOW_HUMAN_HANDOFF setting. With the setting off, the model is told not to promise a person, and if it writes the sentence anyway, the reply is replaced with one saying a human specialist is not available in this chat.
The third is a turn limit. Once a conversation has 25 AI replies, the next customer message gets a fixed notice that the automated turn limit is reached, and the conversation is handed off. This check does not look at ALLOW_HUMAN_HANDOFF. It exists to stop a customer and a bot from going round in circles indefinitely.
The fourth is WhatsApp, which has its own keyword list per channel. If you have not set one, the defaults are "agent", "human", "support", "help" and "representative", matched anywhere in the message, plus the "talk to agent" buttons. This path also does not consult ALLOW_HUMAN_HANDOFF. The default list is broad: "Can you help me track my order?" contains "help" and will go to a human. If you run WhatsApp, replace the defaults with phrases you actually want to escalate on.
Staff can also escalate manually from the dashboard, which is covered below.
What happens once a handoff fires
All four paths end in the same function in our distribution service. It does five things.
customer message (widget, API, WhatsApp)
|
v
message queue -> LangGraph run
preflight (request pattern) -> generate (fixed-sentence watcher) -> handoff node
| (checks ALLOW_HUMAN_HANDOFF)
v
distribution service
|-- Postgres: conversation.status = HANDOFF, SYSTEM message added
|-- audit log: conversation.handoff_requested (with reason)
|-- outbound webhook: CONVERSATION_HANDOFF
|-- Socket.io: conversation:handoff event to the org's agents room
'-- optional: assign to the least-loaded ADMIN / AGENT / OWNERThe conversation's status changes to HANDOFF and a system message is written into the transcript. An audit log entry records the reason (ai_unable, ai_turn_limit_reached, whatsapp_keyword_trigger or manual_escalation). A CONVERSATION_HANDOFF webhook goes to any endpoint you subscribed. Everyone connected to the dashboard for that organization gets a real-time conversation:handoff event over Socket.io, which is what makes the chat appear in the handoff queue without a refresh.
From that point the AI stays quiet. The reply worker only runs for conversations that are OPEN and have no assigned agent, so further customer messages wait for a person. In the dashboard, agents can filter for unassigned handoffs or their own tickets, claim a conversation and reply in the same thread. Claiming is transactional: if a colleague already has the conversation, the claim is refused with their name unless the agent explicitly forces it, which avoids two people answering the same customer.
Automatic handoffs land unassigned. Auto-assignment exists for manual escalations from the dashboard: it picks the ADMIN, AGENT or OWNER in the organization with the fewest OPEN or HANDOFF conversations currently assigned to them. It does not know who is online, on lunch or off shift, so treat it as a tiebreaker, not a rota.
What we have not built
We would rather you hear the gaps from us than discover them during an incident.
There is no handoff summary. The agent who claims the chat reads the transcript. For short conversations that is fine. For a long one that hit the 25-turn limit, it is a lot of scrolling, and a summary generated by the same model that just failed the customer would need its own guardrails before we would trust it.
There is no sentiment or frustration detection. A customer who writes "this is useless" three times, without asking for a person, stays with the AI until one of the four triggers fires. We have not added a frustration classifier because the cheap ones are noisy in both directions: they flag sarcasm and ordinary complaints, and they miss polite customers who are quietly giving up. We would rather make the explicit request easy and honor it every time.
There is no availability check on the web widget path. A handoff at 3 a.m. puts the conversation in the queue and tells the customer a person is coming, whether or not anyone is on shift. WhatsApp channels can send an out-of-office message to new conversations outside configured business hours; the web widget has no equivalent today.
There is no escalation timer on unclaimed handoffs. Nothing re-alerts anyone if a handed-off conversation sits unclaimed for an hour. The only timer is the general one: any OPEN or HANDOFF conversation with no activity for 24 hours is closed automatically, with a message telling the customer they can reopen it. An unclaimed handoff that nobody touches for a day ends that way, which is tidy for the queue and bad for the customer. If you need a shorter escalation, build it on the webhook (example below).
Webhook deliveries are logged but not retried. If your endpoint is down when a handoff fires, that notification is gone, although the conversation is still in the dashboard queue.
Switching handoff off has a cost. If ALLOW_HUMAN_HANDOFF is off, the widget and API have no route to a person short of the 25-turn limit. The AI will not promise a human, but a customer who asks for one keeps getting AI replies. If you switch the setting off, edit your agent's prompt so it tells customers how to reach you another way.
How to design escalation rules customers trust
Whatever tool you use, a few rules hold up well.
Always honor an explicit request. A customer who asks for a person has already decided the bot is not helping. Making them repeat themselves, or asking "are you sure?", costs trust you will not get back. This is why we match on the customer's own words rather than asking the model whether a handoff is warranted.
Escalate on the kind of request, not just on failure. Refund approvals, account ownership changes, billing disputes, legal threats and safety issues should go to a person even when the knowledge base contains a policy that looks relevant. The AI can quote the refund policy; it should not be the one deciding an exception. Put those categories in your prompt and keep the list short enough that the model applies it consistently.
Treat keyword lists as a precision and recall trade-off. Broad words like "help" and "support" catch everyone, including customers the AI could have answered. Narrow phrases miss people who phrase things differently. Start narrow, then read a week of transcripts where customers asked for a person in words your list missed, and add those.
Use a turn limit as a loop breaker, not a quality metric. Twenty-five AI replies is a long conversation for a support question. If many conversations reach it, the problem is usually a documentation gap or a question the AI should never have tried to answer, and the fix belongs in your content, not in a lower limit.
Be honest about the wait. If nobody is on shift, say so and give an expected response time. Silence after "connecting you now" is worse than a clear "our team replies within one business day".
Setting it up in LayBuild
- First, check that handoff on request (
ALLOW_HUMAN_HANDOFF) is still on in the admin settings. It is on by default, and it is what lets customers in the widget and API ask for a person. If you switch it off, edit the prompt as described above. - Next, if you use WhatsApp, replace the default handoff keywords on the channel with phrases you want to escalate on.
- Then make sure the people who should answer handoffs have the AGENT, ADMIN or OWNER role in your organization, and that someone keeps the dashboard open during your support hours so the real-time alert reaches a person.
- Finally, subscribe a webhook to
CONVERSATION_HANDOFFif you want alerts outside the dashboard, such as a message in your team chat or a page to whoever is on call.
A minimal receiver looks like this. It verifies the signature, then forwards the event to an alerting endpoint of your choice. The signature is an HMAC-SHA256 of the timestamp header, a dot, and the raw request body, sent as sha256=<hex>.
import { createHmac, timingSafeEqual } from 'node:crypto';
const WEBHOOK_SECRET = process.env.LAYBUILD_WEBHOOK_SECRET ?? '';
const ALERT_URL = process.env.ALERT_URL ?? '';
function isValidSignature(rawBody: string, timestamp: string, header: string): boolean {
const expected = `sha256=${createHmac('sha256', WEBHOOK_SECRET).update(`${timestamp}.${rawBody}`).digest('hex')}`;
const a = Buffer.from(expected);
const b = Buffer.from(header);
// Constant-time compare so the check does not leak how many characters matched.
return a.length === b.length && timingSafeEqual(a, b);
}
export async function handleWebhook(req: Request): Promise<Response> {
const rawBody = await req.text();
const timestamp = req.headers.get('x-webhook-timestamp') ?? '';
const signature = req.headers.get('x-hub-signature-256') ?? '';
if (!isValidSignature(rawBody, timestamp, signature)) {
return new Response('invalid signature', { status: 401 });
}
const envelope = JSON.parse(rawBody) as {
event: string;
id: string;
timestamp: string;
data: { id: string; reason?: string; assignedAgent?: { name: string } | null };
};
if (envelope.event === 'CONVERSATION_HANDOFF' && !envelope.data.assignedAgent) {
// Only alert on unassigned handoffs; a claimed conversation already has an owner.
await fetch(ALERT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `Chat ${envelope.data.id} needs a person (reason: ${envelope.data.reason ?? 'unknown'})`,
}),
});
}
return new Response('ok');
}Store the delivery id (envelope.id) if you want to ignore duplicates, and reject timestamps that are far from your server's clock if replayed requests worry you. Because we do not retry failed deliveries, keep the handler fast and move slow work to a queue on your side.
Frequently asked questions
Which platforms allow you to escalate from an AI bot to a human agent?
LayBuild, Intercom, Help Scout, and Zendesk all let a human take over from the AI. In LayBuild, a handoff fires when the customer asks for a person, when the model decides the request needs an action it cannot take (a refund approval, an account change, a dispute, a legal or safety concern), after 25 AI replies, or on a WhatsApp handoff keyword. The conversation moves to HANDOFF status and your team is alerted in the dashboard, and through a webhook if you subscribed one. Handoff on request is on by default. An admin can switch it off in settings, which stops the first two triggers; the 25-turn limit and WhatsApp keywords hand off regardless.
What are the best practices for AI-to-human handoff triggers?
Always honor an explicit request for a person, without making the customer repeat it or confirm it. Escalate on the kind of request, not just on failure: refund approvals, account ownership changes, billing disputes, legal threats and safety issues should go to a person even when the knowledge base has a policy that looks relevant, so list them in your prompt and keep the list short.
Treat keyword lists as a precision and recall trade-off: start narrow and add the phrases customers actually used. Use a turn limit (25 AI replies in LayBuild) as a loop breaker, not a quality metric. And be honest about the wait when nobody is on shift.
Does a human handoff preserve conversation history?
Yes. In LayBuild the conversation stays in the same thread. The agent who claims it in the dashboard reads the full transcript of customer messages and AI replies and answers in that thread, so the customer does not have to start over. There is no AI-written summary, so on a long conversation the agent reads the whole history.
Related reading
- Custom webhooks for support automation covers the four webhook events and signature checks in more detail.
- Preventing hallucinations in customer support explains the fixed no-answer reply that often comes just before a customer asks for a person.
- Measuring CSAT in AI-driven channels shows how to read handoff rate next to satisfaction scores.

