📁 last Posts

n8n Human-in-the-Loop: Approval Workflows for AI Agents (2026)

n8n Human-in-the-Loop: Approval Workflows for AI Agents (2026)

Automation has one fatal flaw: it never hesitates. Your AI agent doesn't feel a twinge of doubt before emailing a client or deleting a row — it just does it. The fix isn't slowing the agent down. It's giving it a supervisor: human-in-the-loop approval, the checkpoint where a person says yes or no before the machine acts.

The Direct Answer

n8n has built-in human-in-the-loop: a workflow pauses at a checkpoint, sends an approval request through a channel you choose (Telegram, Slack, Gmail, or the n8n Chat), and resumes only when a person taps Approve or Deny. Since n8n 2.6, you can also attach this review directly to an AI Agent's tools — the agent shows exactly what parameters it wants to use, and the tool only executes on approval.

The details that separate a working checkpoint from a hung workflow: timeout handling (section 8), capturing who approved (section 6), and teaching the agent how to behave after a denial (section 4). The full importable template is in section 9.


1. Which Actions Need Approval

Approval checkpoints slow things down — add them where the cost of a mistake outweighs the cost of waiting. The rule of thumb that has survived every agent project I've run:

⚠️
Any agent action that costs money, sends a communication, or modifies a system of record gets a checkpoint — until the agent has proven its accuracy above your reliability threshold. Sending emails, posting to social media, refunding payments, deleting data, deploying changes: approval. Reading data, summarizing, drafting: no approval.

Apply the same lens to non-agent workflows. A nightly backup doesn't need a human. A workflow that deletes inactive users — that one does.


2. The 3 Ways to Do HITL in n8n

n8n gives you three approaches, from native to DIY. Know all three — each earns its place:

ApproachHow it worksBest for
1. Human review on AI Agent tools (n8n 2.6+) Built-in review step attached to the agent's tool connector Gating specific high-risk agent tools — the default for new agent builds
2. Chat node — Send & Wait Workflow pauses; user replies in the n8n Chat with buttons or text Chat-based agents; both workflow-level and tool-level checkpoints
3. Two-stage workflows Workflow A sends the request and ends; Workflow B reacts to the answer Long wait windows (hours/days) — no hung executions

The honest guidance: approaches 1 and 2 are easier and native — use them first. Approach 3 (section 9) is the escape hatch for when approvals must survive days, because a workflow paused for days is an execution waiting to time out, get pruned, or tie up a queue-mode worker.


3. Native #1: Human Review on AI Agent Tools

Since n8n 2.6 (January 2026), the cleanest approval in the whole platform is built into the AI Agent itself. Here's the flow:

1

Open the agent's Tools connector

On your AI Agent node, click the + on the Tools connector and scroll to the Human review section.

2

Pick the approval channel

Select Telegram, Slack, Gmail, or Chat — and configure it with the relevant credentials. This is where the approval requests will arrive.

3

Attach the tools that need oversight

Connect each high-risk tool to the review step's tool connector and configure it normally. From now on: when the agent wants that tool, the workflow pauses, the reviewer sees which tool and with what parameters, and the tool runs only after Approve.

On denial, the tool call is canceled and the AI is informed of the rejection — which is exactly where the next section begins.


4. Teaching the Agent to Handle Denials

The most overlooked part of HITL is the prompt. A denied agent with no instructions does the dumbest possible thing: it retries the same tool, gets denied again, and loops until your token budget cries. Your system prompt needs explicit denial behavior:

If an approval for a tool is denied, do NOT retry that tool.
Instead, inform the user of the rejection, suggest an alternative,
or ask the user for clarification — whichever fits the situation.
Never loop on a denied tool call.

Also describe which tools require approval and why — an agent that knows "sending an email needs a human" asks cleaner questions and sets better user expectations.


5. Native #2: The Chat Node (Send & Wait)

The n8n Chat has two human-in-the-loop operations, and they fit both positions in a workflow:

  • Send a message — notify the user and continue; no pause.
  • Send a message and wait for response — pause execution until the user replies, with free text or inline approval buttons you define in the node.

Two placements, matching the two philosophies:

PlacementHowWhat it gates
Workflow-levelAdd the Chat node to the main connector after any nodeA broad checkpoint anywhere in the flow — "human, is this output good enough to continue?"
Tool-levelAdd it via the AI Agent's Tools connectorOne specific tool call — the agent pauses before that single action

Setup detail that trips people up: for the Chat node to pause and reply mid-workflow, the Chat Trigger must run with Response Mode: Using Response Nodes — set in the trigger's parameters. Without it, the chat answers once and the wait never happens.


6. Slack Approvals (Full Setup)

Slack is the favorite approval channel for teams — and the only one with real setup requirements. From the official changelog, the complete checklist:

1

Make n8n reachable over public HTTPS

Slack pushes the approval tap to your server — so your n8n must have a public URL. If you're behind CGNAT, tunnel it (Cloudflare Tunnel) or use Telegram instead.

2

Enable Interactivity in your Slack app

In api.slack.com: Interactivity & Shortcuts → ON, and set the Request URL to https://your-n8n-domain/webhook-waiting-slack — that exact path.

3

Paste the signing secret into the credential

Copy your Slack app's Signing Secret into the Signature Secret field of the Slack credential in n8n. Skip this and every approval tap arrives unsigned — and gets rejected.

4

Configure the node

On the Slack node: Response Type → Approval, then in Advanced Interactivity enable Capture Who Responded — the output then records the approver's ID, name, username, channel, message ID, and email (when scopes allow).

5

Restrict who may approve

List allowed user IDs in Restrict Who Can Approve. Everyone else sees the request; only listed users can act. For refunds and deployments, this is the whole point.


7. Telegram Approvals (Full Setup)

Telegram approvals work the same way but with one advantage: no public HTTPS requirement — your bot's webhook setup already handled that, or the polling community node avoids it entirely (both covered in our Telegram guide).

  • Operation: choose Send and Wait for Response on the Telegram node, with approval buttons in the reply markup.
  • One-tap approvals: the approver taps Approve/Deny inside Telegram itself — no links, no forms.
  • Capture Who Responded: enable it in the same Advanced Interactivity section as Slack — the output records who tapped, their username, chat, and message ID.
  • Restrict Who Can Approve: list allowed Telegram user IDs; everyone else's taps are ignored.

If your approval needs to reach a single person, DM them directly. If a team should share the load, route requests to a private group — the approvers get one shared queue, and Capture Who Responded tells you which of them acted.


8. Timeouts & Auto-Decisions

Every approval request will, one day, go unanswered. Design for it before it happens:

  • Short waits (minutes–hours): native review steps hold the execution. Acceptable while your execution timeouts allow it.
  • Long waits (days): a paused workflow is a hostage — it occupies an execution slot and can hit the execution timeout. Use the two-stage pattern (section 9): the requesting workflow ends after sending; a second workflow reacts whenever the answer arrives. Nothing hangs.
  • Auto-decisions: decide your policy in advance — auto-approve when the risk is low and the delay is worse than the mistake; auto-reject when the action is irreversible. Write it down, because at 2 AM you'll be tempted to improvise.
ℹ️
Queue mode note: if you run queue mode (our Queue Mode guide), a paused execution holds a worker slot. Long approval windows are exactly the case for the two-stage pattern.

9. Free Template: Two-Stage Telegram Approval

The two-stage pattern from section 8, implemented: Workflow A sends the approval request with Approve/Reject buttons and finishes; Workflow B listens for the tap and executes or cancels. No hung executions, works on every n8n version, and every node type below matches verified exports.

Workflow A — Request Approval

{
  "name": "Approval Requester",
  "nodes": [
    {
      "parameters": {},
      "id": "manual",
      "name": "When Tested / Triggered",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [250, 300]
    },
    {
      "parameters": {
        "jsCode": "// Replace with the action your workflow is proposing\nconst action = 'Publish post #42 to LinkedIn';\nconst chatId = 'YOUR_TELEGRAM_CHAT_ID'; // personal ID or private group\n\nconst payload = {\n  chat_id: chatId,\n  text: `🔍 Approval needed:\\n\\n${action}\\n\\nApprove or reject:`,\n  reply_markup: {\n    inline_keyboard: [[\n      { text: '✅ Approve', callback_data: 'approve_42' },\n      { text: '❌ Reject', callback_data: 'reject_42' }\n    ]]\n  }\n};\n\nreturn [{ json: { body: JSON.stringify(payload) } }];"
      },
      "id": "build",
      "name": "Build Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [450, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_TOKEN }}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.body }}",
        "options": {}
      },
      "id": "send",
      "name": "Send Request (Bot API)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [650, 300]
    }
  ],
  "connections": {
    "When Tested / Triggered": {
      "main": [
        [
          {
            "node": "Build Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Request": {
      "main": [
        [
          {
            "node": "Send Request (Bot API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true
  }
}

Workflow B — Handle the Answer

{
  "name": "Approval Handler",
  "nodes": [
    {
      "parameters": {
        "updates": ["callback_query"],
        "additionalFields": {
          "download": true
        }
      },
      "id": "trigger",
      "name": "Telegram Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [250, 300],
      "credentials": {
        "telegramApi": {
          "id": "YOUR_TELEGRAM_CREDENTIAL_ID"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const item = $input.item.json;\nconst cq = item.callback_query;\nconst data = cq.data;                 // 'approve_42' | 'reject_42'\nconst approved = data.startsWith('approve');\nconst actionId = data.split('_')[1] || data;\n\nreturn [{\n  json: {\n    approved,\n    actionId,\n    callbackQueryId: cq.id,\n    chatId: cq.message.chat.id,\n    approverName: cq.from.first_name || 'Unknown'\n  }\n}];"
      },
      "id": "parse",
      "name": "Parse Tap",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [450, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_TOKEN }}/answerCallbackQuery",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"callback_query_id\": \"{{ $json.callbackQueryId }}\", \"text\": \"Thanks!\"}",
        "options": {}
      },
      "id": "answer",
      "name": "Close Spinner",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [650, 300]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.approved }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "if-approved",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [850, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_TOKEN }}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"chat_id\": \"{{ $('Parse Tap').item.json.chatId }}\", \"text\": \"✅ Approved by {{ $('Parse Tap').item.json.approverName }} — action #{{ $('Parse Tap').item.json.actionId }} is running.\"}",
        "options": {}
      },
      "id": "notify-approve",
      "name": "Notify Approved",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1050, 200]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_TOKEN }}/sendMessage",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"chat_id\": \"{{ $('Parse Tap').item.json.chatId }}\", \"text\": \"❌ Rejected by {{ $('Parse Tap').item.json.approverName }} — action #{{ $('Parse Tap').item.json.actionId }} cancelled.\"}",
        "options": {}
      },
      "id": "notify-reject",
      "name": "Notify Rejected",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1050, 400]
    }
  ],
  "connections": {
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Parse Tap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Tap": {
      "main": [
        [
          {
            "node": "Close Spinner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Close Spinner": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Notify Approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true
  }
}
  • Where your real action goes: replace the "Notify Approved" message with the actual execution — publish the post, run the refund, delete the rows. The two notify nodes are the placeholders for your approve/reject branches.
  • One token, one env var: both workflows read TELEGRAM_TOKEN from the environment — nothing sensitive sits in the JSON (same pattern as our Telegram guide).
  • Scale it up: put the action ID and the actual payload into the callback data, or store pending actions in a sheet/database and look them up on approval — the two-stage structure stays the same.

10. The 8 Most Common Errors & Fixes

#Error / SymptomCauseFix
1 Workflow hangs for days waiting for approval No timeout, no escape — the execution just waits Use the two-stage pattern for long windows, or define an explicit auto-decision policy (section 8)
2 Approval buttons do nothing / spinner spins forever The callback isn't answered Call answerCallbackQuery (the template's "Close Spinner" node) before anything else
3 Agent executes the tool without any approval Tool not attached to the human review step, or n8n version older than 2.6 Attach the tool to the review connector; update n8n — tool review shipped in 2.6
4 Slack approvals never arrive Interactivity off, wrong Request URL, or missing signing secret The full 5-step Slack checklist in section 6 — including the /webhook-waiting-slack path
5 Denied action still executes Branching on the wrong field after the review node Read the approved flag from the review output (e.g., $json.data.approved), not from memory
6 Can't tell who approved what Capture Who Responded not enabled Enable it in Advanced Interactivity (Slack/Telegram) — the output carries ID, name, username, and message ID
7 Agent loops: asks approval, gets denied, asks again System prompt has no denial behavior Add explicit denial instructions to the prompt (section 4)
8 Chat approvals never pause the chat Chat Trigger isn't in Response Mode: Using Response Nodes Set the trigger's response mode, then the Chat node's wait operation works (section 5)
ℹ️
An approval workflow failing silently means actions never run and nobody knows. Route its errors through an Error Trigger — the n8n Error Handling guide builds the complete alerting setup.

11. Frequently Asked Questions

What is human in the loop in n8n?

Human-in-the-loop is a built-in n8n feature that pauses a workflow at a checkpoint and waits for a person to approve or deny before continuing. The workflow sends a notification to a channel you configure — Telegram, Slack, Gmail, or the n8n Chat — and halts until the reviewer responds (sections 2–7).

How do I add human approval to an AI agent tool?

Open the AI Agent node, click the + on the Tools connector, scroll to Human Review, and select your approval channel. Attach the tools that need oversight to the review step. The agent then pauses and shows the reviewer exactly what parameters it wants to pass before the tool runs (section 3).

Which channels can I use for approval requests in n8n?

Telegram, Slack, Gmail, and the n8n Chat interface. Slack and Telegram support single-tap in-app approval buttons and can record who approved, when, and from where. Slack requires your n8n instance to be reachable over public HTTPS (sections 6–7).

Can I capture who approved an action?

Yes. On Slack and Telegram, enable Capture Who Responded in the Advanced Interactivity settings of the Send and Wait for Response operation. The node output records the approver's ID, name, username, channel, and message ID — and for Slack, their email when scopes allow.

Can I restrict who is allowed to approve?

Yes. List allowed user IDs in the Restrict Who Can Approve setting. Everyone else sees the request but cannot act on it — useful when only managers should approve refunds or deployments.

What happens when the human denies an approval?

The tool call is canceled and the AI is informed of the rejection. The agent's behavior after denial depends on your system prompt — tell it to inform the user, suggest alternatives, or ask for clarification instead of retrying the same tool in a loop (section 4).

What happens if nobody responds to an approval request?

That depends on your design. The native review step waits until a response arrives — which can hang a workflow for days. For long wait windows, use the two-stage pattern (two short workflows instead of one long-lived execution) or configure a timeout with an explicit auto-approve or auto-reject policy (section 8).

Does Slack approval require a public HTTPS URL?

Yes. Your n8n instance must be reachable from Slack over public HTTPS. Turn on Interactivity in your Slack app, set the Request URL to your n8n address with /webhook-waiting-slack, and paste your Slack signing secret into the Slack credential (section 6).

Should I use tool-level or workflow-level approval?

Tool-level approval gates one specific tool call — the agent must get a thumbs-up before that tool executes. Workflow-level approval pauses the entire workflow at a checkpoint. Use tool-level for high-risk agent tools; workflow-level for broad checkpoints anywhere in the flow (section 5).

Does the agent know when its approval was denied?

Yes. When a reviewer denies, the tool call is canceled and the rejection is passed back to the agent. The system prompt decides what happens next — so write explicit denial behavior: inform the user, offer alternatives, or ask for clarification (section 4).


Sources & References

Explore More Guides

Ahmed Ayari — TriggerWorkflow The two-stage pattern in section 9 exists because of a hung execution — a paused workflow that sat waiting for an approval for three days and quietly died with the execution timeout, taking its context with it. Since then, every long-window approval on TriggerWorkflow.com runs as two short workflows. None of the denial-handling advice in section 4 is theoretical either: it's what stopped our agent from begging for approval in a loop. Read more guides by Mr.Ayari
Comments