Human Handoffs
AI does the work. Humans approve the decisions.
The human handoff system lets a workflow pause execution, notify a human reviewer with full context, and wait for approval or rejection before continuing. The reviewer sees exactly what happened in the conversation, why the handoff was triggered, and has one-click approve/reject controls.
How It Works
- The workflow reaches a
human_handoffaction node - Execution pauses — the workflow state is persisted
- A notification is sent to the configured reviewers with:
- The escalation reason (from the node’s
reasonfield) - The full conversation history
- Relevant variables (order details, amounts, customer info)
- Approve and Reject buttons
- The escalation reason (from the node’s
- The reviewer reviews the context and makes a decision
- The workflow resumes on the approved or rejected edge
The user receives a message like: “I have escalated this to a reviewer. You will hear back shortly.”
Setting Up the human_handoff Node
Add a human_handoff action to your workflow:
{
"type": "action",
"data": {
"actionType": "human_handoff",
"reason": "Refund of ${{refund_amount}} for order #{{order_id}} (customer: {{verified_email}})",
"label": "Finance Approval"
}
}The reason field supports variable interpolation. Include the key details the reviewer needs to make a decision without leaving the notification interface.
Connecting Approved and Rejected Paths
Wire edges from the handoff node to downstream nodes:
{ "source": "handoff_1", "target": "process_action", "sourceHandle": "approved" },
{ "source": "handoff_1", "target": "send_rejection", "sourceHandle": "rejected" }The approved path executes when the reviewer approves. The rejected path executes when the reviewer rejects.
Notification Channels
Reviewers can be notified through multiple channels. Configure notification preferences in your bot’s settings.
| Channel | How It Works |
|---|---|
| Web Dashboard | Notification appears in the Admin Dashboard under the bot’s handoff queue. Reviewer clicks Approve or Reject directly. |
| Reviewer receives an email with the conversation summary, reason, and action links. | |
| Telegram | Notification sent to a Telegram user or group with inline Approve/Reject buttons. |
| Discord | Notification sent to a Discord channel with button components. |
What the Reviewer Sees
The notification includes:
| Field | Content |
|---|---|
| Bot name | Which bot triggered the handoff |
| Reason | The interpolated reason string from the node |
| Conversation summary | Recent messages between the user and the bot |
| Key variables | Order details, amounts, customer info — whatever variables are set |
| Timestamp | When the handoff was triggered |
| Actions | Approve / Reject buttons |
The reviewer does not need to log in to the full dashboard to take action. Email and messaging notifications include direct action links.
Use Cases
High-Value Refund Approval
A customer asks for a $2,000 refund. The bot:
- Verifies identity via
auth_gate - Looks up the order and confirms eligibility
- Detects the amount exceeds $500
- Triggers
human_handoffwith reason: “Refund of $2,000 for order #45678” - The finance manager receives a Telegram notification
- Manager reviews the order details and approves
- The workflow resumes and processes the refund via Stripe MCP
Contract Review
A sales bot generates a proposal for a prospect. Before sending:
- The workflow assembles the proposal details
human_handoffnotifies the legal team: “New proposal for Enterprise plan, $50k/year”- Legal reviews terms and approves
- The bot sends the approved proposal to the prospect
Access Requests
An IT helpdesk bot receives a request for production database access:
- Bot verifies the requester’s identity
- Checks their role and team via LDAP MCP
human_handoffnotifies the security team with the requester’s name and team- Security approves or rejects with a reason
Compliance Review
A content moderation bot flags a post:
- AI analyzes the content and detects potential policy violation
human_handoffsends the content to the moderation team- Moderator reviews and decides: approve (content stays) or reject (content removed)
Escalation from Support
Any support interaction the bot cannot resolve:
- Bot attempts to help via agent loop or workflow
- After failing to resolve, triggers
human_handoff - Support agent picks up the conversation with full context
Best Practices
Write Descriptive Reasons
The reason string is the most important part of the notification. Include:
- What is being requested
- Who is requesting it
- How much is at stake (dollar amounts, access levels, etc.)
Good: "Refund of $2,150 for order #45678 — customer verified as [email protected], order placed 3 days ago"
Bad: "Customer wants a refund"
Set Timeout Expectations
The user waits while the handoff is pending. Communicate clearly:
- Tell the user their request has been escalated
- Provide an estimated response time if possible
- Offer alternative contact methods for urgent issues
Keep the Approved/Rejected Paths Complete
Both paths should send a message to the user:
- Approved: “Your refund has been approved and processed.”
- Rejected: “Your request was reviewed but could not be approved at this time. Please contact [email protected] for more details.”
Never leave the user without a response on either path.
Use auth_gate Before human_handoff
For any workflow that handles sensitive actions, verify the user’s identity before escalating. This ensures the reviewer sees a verified identity, not just a platform username.
Example: Complete Refund Workflow with Handoff
{
"nodes": [
{ "id": "t1", "type": "trigger", "data": { "triggerType": "message_received" } },
{ "id": "auth", "type": "auth_gate", "data": { "provider": "resend", "contact_type": "email", "outputVariable": "email" } },
{ "id": "lookup", "type": "mcp_tool", "data": { "toolName": "orders__get_order", "arguments": "{\"email\": \"{{email}}\"}", "outputVariable": "order" } },
{ "id": "check", "type": "logic", "data": { "logicType": "if_else", "condition": "{{order.total}} > 500" } },
{ "id": "handoff", "type": "action", "data": { "actionType": "human_handoff", "reason": "Refund ${{order.total}} for order #{{order.id}} ({{email}})" } },
{ "id": "auto_refund", "type": "mcp_tool", "data": { "toolName": "stripe__create_refund", "arguments": "{\"order_id\": \"{{order.id}}\"}", "outputVariable": "refund" } },
{ "id": "approved_refund", "type": "mcp_tool", "data": { "toolName": "stripe__create_refund", "arguments": "{\"order_id\": \"{{order.id}}\"}", "outputVariable": "refund" } },
{ "id": "msg_approved", "type": "send_message", "data": { "message": "Your refund of ${{order.total}} has been approved and processed." } },
{ "id": "msg_rejected", "type": "send_message", "data": { "message": "Your refund request was reviewed but could not be approved. Please contact [email protected]." } },
{ "id": "msg_auto", "type": "send_message", "data": { "message": "Your refund of ${{order.total}} for order #{{order.id}} has been processed." } }
],
"edges": [
{ "id": "e1", "source": "t1", "target": "auth" },
{ "id": "e2", "source": "auth", "target": "lookup", "sourceHandle": "verified" },
{ "id": "e3", "source": "lookup", "target": "check" },
{ "id": "e4", "source": "check", "target": "handoff", "sourceHandle": "true" },
{ "id": "e5", "source": "check", "target": "auto_refund", "sourceHandle": "false" },
{ "id": "e6", "source": "handoff", "target": "approved_refund", "sourceHandle": "approved" },
{ "id": "e7", "source": "handoff", "target": "msg_rejected", "sourceHandle": "rejected" },
{ "id": "e8", "source": "approved_refund", "target": "msg_approved" },
{ "id": "e9", "source": "auto_refund", "target": "msg_auto" }
]
}Next Steps
- Workflow Node Types — Full
human_handoffandauth_gatereference - Bot Teams & Delegation — Combine handoffs with bot delegation
- Workflow Recipes — More complete examples