n8n Human in the Loop Tutorial: Slack and Form Approval, Tested Approve / Reject / Timeout

Add human approval to n8n AI agents with the Wait node and Slack buttons, so nothing risky runs without a yes.

Illustration showing AI automation passing through a human approval checkpoint.

Updated on: .

Disclosure: Operant Solo is reader-supported. We may earn an affiliate commission when you purchase through links on this page, at no additional cost to you. Pages that include our own dated test say so and link the test files; other pages are based on vendor documentation and public pricing, which can change.

Quick answer: For n8n human in the loop, pause the workflow at a Wait node (form) or Slack “Send and Wait for Response,” then branch on three separate outcomes: approved, rejected and timeout. Only approved may continue. In our Cloud tests, rejection and timeout both blocked the send. Run a patched n8n version, because approval-gate bugs were fixed in September 2026.

Free workflow included below: a form lab you can run without another account, and a Slack lab for teams, both tested for approve, reject, and timeout.

Your workflow has written an email. Before it sends, you want a person to read the exact draft and choose Approve or Reject. That is the job of an n8n human-in-the-loop step. This guide shows a Wait form and Slack approval, with a free workflow you can copy and real results from both routes.

Which n8n human-in-the-loop node should you use?

  • Start with a review form: use the Wait node when you want to try the included lab without connecting another account.
  • Ask a teammate in Slack: use Slack Send and Wait for Response. It sends the request and waits in one node.
  • Review an AI Agent tool call: use the agent’s Human review connection for the specific tool. The setup differs from the two tested labs below.

Pick the route that matches your task. Then test approval, rejection and no response before adding a real send or update node.

Updated October 8, 2026. The form lab below was tested in n8n Cloud on October 7. The native Slack approval lab was tested in n8n Cloud on October 8: approve, reject and no response all took the expected route. The two-minute expiry test was then restored to 24 hours. These were desktop browser tests with fictional data; no draft was published.

Tested (Cloud, 7-8 Oct 2026)Not tested
Form: approve, reject, no response (55.4 s shortened timeout)Full 24-hour timeout run to completion
Slack: executions 339 (approve), 340 (reject), 341 (timeout, 2 min 3.9 s)Phone approval; identity-restricted approval
Fictional data, no external actionsDuplicate callbacks and real downstream sends in production

How to build an n8n human-in-the-loop workflow

  1. Prepare the draft. Keep its recipient, job ID, revision and expiry time together.
  2. Request a decision. Use a native Send and Wait for Response node, or a Wait node with a review form.
  3. Set a deadline. Enable a 24-hour limit so an unanswered request does not wait forever.
  4. Check the result. Treat approve, reject and timeout as separate outcomes.
  5. Gate the final action. Only the approved branch can send or update anything. Recheck the draft and prevent duplicate writes first.

Start with the credential-free lab below. It uses a fictional reminder and records a decision without sending email, posting to Slack or calling an AI model.

Try the tested approval form first

The four nodes are Manual Trigger → Approval context → Review draft → Check decision. The context stores a fictional draft and a deadline. The Wait node displays an Approve/Reject form. The final Code node returns a decision and a boolean called sendAllowed.

This is a learning workflow, not a complete client-email automation. It has no sending node, external credentials or identity check for the reviewer. Keep the review link private. Before using it for real work, add appropriate authentication and an authorized-reviewer check.

  1. Expand the JSON below and copy the whole object.
  2. Open a blank n8n workflow and paste it onto the canvas, or save it as a .json file and use Import → From file.
  3. Wait for the imported nodes to finish loading, then execute the workflow.
  4. Open Review draft. While it is waiting, use the form link shown in the output panel.
  5. Choose Approve or Reject and submit. Open Check decision to inspect the result.
Copy the complete approval form workflow JSON
{
  "name": "OperantSolo approval lab — form, no external actions",
  "nodes": [
    {
      "id": "manual",
      "name": "Run approval lab",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "parameters": {}
    },
    {
      "id": "prepare",
      "name": "Approval context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ],
      "parameters": {
        "jsCode": "return [{json:{jobId:'approval-lab-001',draft:'Fictional reminder: the team workshop starts at 10am.',expiresAt:new Date(Date.now()+24*60*60*1000).toISOString()}}];"
      }
    },
    {
      "id": "review",
      "name": "Review draft",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        480,
        0
      ],
      "webhookId": "operantsolo-approval-lab",
      "parameters": {
        "resume": "form",
        "formTitle": "OperantSolo approval lab",
        "formDescription": "Review a fictional workshop reminder. This lab records your choice; it never sends a message or changes a calendar.",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Decision",
              "fieldType": "dropdown",
              "fieldOptions": {
                "values": [
                  {
                    "option": "Approve"
                  },
                  {
                    "option": "Reject"
                  }
                ]
              },
              "requiredField": true
            }
          ]
        },
        "limitWaitTime": true,
        "limitType": "afterTimeInterval",
        "resumeAmount": 24,
        "resumeUnit": "hours",
        "options": {}
      }
    },
    {
      "id": "decision",
      "name": "Check decision",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        0
      ],
      "parameters": {
        "jsCode": "const job=$('Approval context').first().json;\nconst response=$input.first().json;\nconst choice=response.Decision ?? response.body?.Decision;\nconst expired=Date.now()>=Date.parse(job.expiresAt);\nconst decision=expired || !choice ? 'timeout' : choice==='Approve' ? 'approved' : 'rejected';\nreturn [{json:{jobId:job.jobId,decision,sendAllowed:decision==='approved',externalActions:0,note:'Demo only: no message was sent.'}}];"
      }
    }
  ],
  "connections": {
    "Run approval lab": {
      "main": [
        [
          {
            "node": "Approval context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approval context": {
      "main": [
        [
          {
            "node": "Review draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Review draft": {
      "main": [
        [
          {
            "node": "Check decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}

Each execution gets its own review link. Do not reuse an old execution’s link. For a custom notification before the Wait node, use $execution.resumeFormUrl for a form or $execution.resumeUrl for a webhook. These are generated at runtime; there is no permanent approval URL to copy before the workflow starts.

Real n8n approval form with an Approve or Reject dropdown
Actual n8n browser form from the fictional reminder lab. No Slack message or client email is sent.

What the real lab returned

TestObserved decisionAllowed to send?
Approve formapprovedtrue; no external action exists in this lab
Reject formrejectedfalse
No responsetimeoutfalse

Approval and rejection were submitted through the actual n8n form. The no-response test used a shorter timeout setting instead of waiting a full day. Its execution completed in 55.432 seconds, including scheduling and execution overhead. The reusable JSON is configured for 24 hours. This test verifies the decision routing; it does not establish a precise timeout service-level guarantee.

n8n approval lab output showing timeout and sendAllowed false
Actual no-response test output: timeout, sendAllowed false and zero external actions.

Configure n8n Slack approval

For a team already using Slack, start with the native Slack → Message → Send and Wait for Response operation and choose Approval as the response type. It handles the request and pause in one node, so this route does not need a separate Wait node.

Enable both choices: open Approval Options, add Values, then set Type of Approval to Approve and Disapprove. Change the Disapprove Button Label to Reject if you want that wording. The default is Approve Only, which does not show a rejection button.

Use a private test channel first. Include the exact draft, intended recipient and revision in the request. A button labelled Approve should approve a specific action, not a vague summary. Keep the original job in an earlier node called Approval context so you can retrieve it after the response.

The default link-based approval and advanced inline Slack buttons have different setup requirements. If you need verified responder details or restricted approvers, check the current n8n Slack approval instructions. Advanced interactivity requires the Slack app configuration and signing secret described there. Do not assume a forwarded link proves who approved.

What happened in our real Slack test

We connected Slack to an isolated four-node lab: Manual Trigger → Approval context → Slack review → Check decision. The request went to a private channel. We used the standard link-based buttons, so clicking a button opened an n8n page that recorded the decision. Advanced interactivity was off.

  • Approve: execution 339 returned data.approved: true. The decision was approved and sendAllowed was true.
  • Reject: execution 340 returned data.approved: false. The decision was rejected and sendAllowed was false.
  • No response: execution 341 finished in 2 minutes 3.893 seconds with the limit set to two minutes. The response had no approval boolean, so the decision was timeout and sendAllowed was false.

All three runs stopped after recording the result. There was no email, publishing or calendar node. The 24-hour setting below is the restored configuration; we did not wait 24 hours for this test. A physical phone and identity-restricted approval were not tested.

Real Slack requests in a private test channel, showing the default Approve button and the added Reject and Approve choices
Real fictional-data requests. The first used the default approval-only setting; the later requests included Reject.
n8n decision output after a real Slack rejection, with sendAllowed false and zero external actions
The real rejection result kept sending disabled.
n8n timeout result after a Slack request received no response, with sendAllowed false
No response took the timeout path and kept sending disabled.

Set the 24-hour deadline

For the included Wait-form workflow, use these exact settings. For native Slack Send and Wait, add its Limit Wait Time option and set the response window to the same 24 hours. Check your node’s output with a short test before connecting a sending action.

SettingValuePurpose
ResumeOn Form SubmittedWait for the review form
Limit Wait TimeOnEnable expiry
Limit TypeAfter Time IntervalUse a relative deadline
Amount / Unit24 / HoursMaximum review window
After resumeExplicit decision checkAn expired request cannot send

Store a matching expiresAt value when preparing the job. For example, in a Code node use new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(). A deadline in the decision check prevents a late approval from authorizing stale work.

Check approve, reject and timeout separately

A native approval response uses data.approved. Compare it to the boolean true; do not accept the string “true” or the presence of any response as approval. Missing approval data must take the stop/escalation path. The form lab uses its own Decision dropdown, so it has different parsing code.

Decision code for a native approval node
// Single-job example. Code node: Run Once for All Items.
const job = $('Approval context').first().json;
const response = $input.first().json;
const deadline = Date.parse(job.expiresAt);
const expired = !Number.isFinite(deadline) || Date.now() >= deadline;
const approved = response.data?.approved;
let decision = 'timeout';
if (!expired && approved === true) decision = 'approved';
else if (!expired && approved === false) decision = 'rejected';
return [{ json: { ...job, decision, sendAllowed: decision === 'approved' } }];

This example processes one job per execution. For multiple items, correlate each response to its job ID instead of taking the first item. Add an IF node with sendAllowed set to is true. Only its true output connects to the final action. Use a Switch on decision for the false path: rejected records the refusal and stops; timeout notifies a backup reviewer and stops.

A useful timeout notice says which job expired, which revision was reviewed and where to open a fresh request. An escalation must never silently approve the old draft. If a backup reviewer approves, validate a new request and its deadline.

Protect the final send from stale or duplicate approvals

Approval is only one check. Immediately before sending, reload the job and confirm that its recipient, content and revision still match what the reviewer saw. If someone edited the message after approval, request another review.

Keep a durable record keyed by job ID and revision. An already-completed job must not send again when a callback is retried. If the downstream service supports an idempotency key, use it. Otherwise design a durable claim/status transition and a recovery check for uncertain sends. A Code node variable alone is not a reliable duplicate-send ledger.

If an API times out after a send, check whether the action succeeded before retrying. Our n8n 429 and Retry-After lab explains why retries also need safety checks. For predictable drafts, see structured outputs in n8n.

Test the paths before connecting real accounts

CaseExpected resultWhat to inspect
Approve valid draftApproved branch onlyJob, revision and final recipient match
RejectStopNo send/update node runs
No responseTimeout and escalationNo automatic approval
Approve after expiryStop or fresh reviewDeadline check fails closed
Duplicate callbackNo second writeDurable job status or idempotency key
Sending API returns uncertain resultInvestigate before retryCheck downstream state

Keep error handling separate from rejection. A credential error or failed notification should alert an operator and stop the sending path. It is not evidence that a reviewer rejected the draft, and it must not count as approval. Use the n8n error workflow guide to plan failure alerts alongside the approval paths.

Patch n8n before you trust an approval gate

An approval step is only as strong as the endpoint that resumes it. n8n published several advisories in September 2026 that affect exactly these gates. Our form and Slack tests did not probe these flaws, so passing those tests does not mean an unpatched instance is safe.

AdvisoryWhat it affectsSeverityFixed in
GHSA-597w-c3jh-g8fgSigned resume URL for Send and Wait could be crafted by someone who can save workflows, producing a valid approve/decline link for a gate in a project they cannot readHigh (CVSS 7.0)1.123.80, 2.39.6, 2.40.1
GHSA-728h-pmr2-7cghSend-and-Wait HMAC check could be skipped, letting a caller holding an execution’s resume token approve itHigh (CVSS 7.0)2.41.4, 2.42.1
GHSA-p3pg-xw4f-m72cA project member could answer another member’s pending agent tool approvalModerate (CVSS 6.1)2.41.4, 2.42.0

n8n’s 1 October 2026 security update lists v1.123.83, v2.41.4 and v2.42.1 as the fixed versions per branch. Cloud instances are patched automatically; self-hosted instances need the upgrade. Confirm the current numbers in the n8n advisories before upgrading, and see the self-hosted patch and hardening checklist.

Until you are patched, n8n’s advisory workarounds are temporary: limit who can edit workflows, avoid approval nodes guarding high-impact actions behind a public trigger, and review execution history for unexpected approvals. Also keep the checks this tutorial already uses: a deadline in the decision code and approval tied to the exact draft.

Human review for AI Agent tool calls

On an AI Agent node, click the Tools connector, add a Human review step, pick a channel, then attach the tools that need approval. n8n’s docs list Chat, Slack, Discord, Telegram, Microsoft Teams, Gmail, WhatsApp Business Cloud, Google Chat and Microsoft Outlook as channels. A reviewer can approve, so the tool runs, or deny, so the action is cancelled and the agent is told it was rejected. Mention approval in the system prompt so the agent handles a denial sensibly. We did not test these channels; our evidence covers the form and Slack Send-and-Wait paths above.

If an AI Agent selects a tool dynamically, use n8n’s native Human review connection for that tool. This lets a reviewer inspect a proposed tool call before it executes. For a fixed workflow where AI only writes a draft, the approval pattern above is often easier to follow.

Review the actual tool arguments: recipient, record ID, amount or text. Approving “the agent’s plan” is too broad when the next tool can change something important. See the official human review tool guide for the connection setup. For deciding which actions should require a person, read our human oversight design guide.

Common setup mistakes

The review link does not work

Check that the execution is still waiting and that you used the runtime link from that execution. Check authentication and any expiry too. A copied link from yesterday’s run does not approve today’s job.

The workflow sends after nobody responds

The decision check is likely treating any continuation as approval. Inspect the native node’s data.approved field or the form’s Decision field. Connect the sending node only to an explicit approved branch.

Do I need PostgreSQL for a Wait node?

No. PostgreSQL is not a mandatory feature requirement for this pattern. Use a supported, persistent n8n database and storage setup. For self-hosting, maintain backups and test recovery after a restart. Do not rely on an ephemeral database volume.

Does self-hosting keep all the data on my server?

No. Slack, email providers and hosted AI APIs still receive the data sent to them by your nodes. Review those connections and send only the information each step needs.

Can I approve from a phone?

A reviewer can use the review form link in a browser. For Slack or email approval, test the exact client your team uses. The screenshots here show the real browser form, not a verified Slack mobile session.

Build your next approval workflow

Run the safe lab first, inspect all three decisions, then add one real action behind the approved branch. Start with an internal draft and check the resulting record yourself. Browse the n8n workflow hub for more practical labs.

Sources: Wait node documentation, Slack node operations, and n8n’s human oversight playbook. Node options can change between versions; check the labels in your installed version.

n8n

Best for: technical automation workflows

Consider n8n when your workflow needs custom logic or control over deployment. Self-hosting also requires time for updates, backups, and monitoring.

Operant Solo may earn a commission if you purchase through this link, at no extra cost to you.

You can read this article without subscribing. Want the next tested article?

Build better n8n workflows

Get a new tested n8n article when it is ready, covering rate limits, error workflows, retries and approvals. Free. Unsubscribe anytime.

Subscriptions are handled by Jetpack. Read our Privacy Policy before signing up.

Scroll to Top