# Human Handoffs

> Pause bot workflows for human approval. Notify reviewers via email, web dashboard, Telegram, or Discord. Approve or reject to resume the workflow on the correct path.

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

```mermaid
flowchart TD
    A[Workflow running] --> B[human_handoff node reached]
    B --> C[Workflow PAUSES]
    C --> D[Notification sent to reviewer]
    D --> E{Reviewer decision}
    E -->|Approve| F[Workflow resumes: approved path]
    E -->|Reject| G[Workflow resumes: rejected path]
    F --> H[Bot continues processing]
    G --> I[Bot sends rejection response]
```

1. The workflow reaches a `human_handoff` action node
2. Execution **pauses** — the workflow state is persisted
3. A notification is sent to the configured reviewers with:
   - The escalation reason (from the node's `reason` field)
   - The full conversation history
   - Relevant variables (order details, amounts, customer info)
   - Approve and Reject buttons
4. The reviewer reviews the context and makes a decision
5. 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:

```json
{
  "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:

```json
{ "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. |
| **Email** | 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:
1. Verifies identity via `auth_gate`
2. Looks up the order and confirms eligibility
3. Detects the amount exceeds $500
4. Triggers `human_handoff` with reason: "Refund of $2,000 for order #45678"
5. The finance manager receives a Telegram notification
6. Manager reviews the order details and approves
7. The workflow resumes and processes the refund via Stripe MCP

### Contract Review

A sales bot generates a proposal for a prospect. Before sending:
1. The workflow assembles the proposal details
2. `human_handoff` notifies the legal team: "New proposal for Enterprise plan, $50k/year"
3. Legal reviews terms and approves
4. The bot sends the approved proposal to the prospect

### Access Requests

An IT helpdesk bot receives a request for production database access:
1. Bot verifies the requester's identity
2. Checks their role and team via LDAP MCP
3. `human_handoff` notifies the security team with the requester's name and team
4. Security approves or rejects with a reason

### Compliance Review

A content moderation bot flags a post:
1. AI analyzes the content and detects potential policy violation
2. `human_handoff` sends the content to the moderation team
3. Moderator reviews and decides: approve (content stays) or reject (content removed)

### Escalation from Support

Any support interaction the bot cannot resolve:
1. Bot attempts to help via agent loop or workflow
2. After failing to resolve, triggers `human_handoff`
3. 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 jane@acme.com, 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 support@acme.com 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

```json
{
  "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 support@acme.com." } },
    { "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](/bots/workflows/node-types) — Full `human_handoff` and `auth_gate` reference
- [Bot Teams & Delegation](/bots/bot-teams) — Combine handoffs with bot delegation
- [Workflow Recipes](/bots/workflows/examples) — More complete examples
