Tips > AI & LLM Integration

Build Human-in-the-Loop AI Workflows

For high-stakes outputs -- legal documents, customer-facing communications, financial summaries -- always route AI-generated content through human review

To add human review to an AI workflow, route the generated content through n8n's Wait node and resume it with a webhook callback. The Wait node pauses execution until a reviewer clicks Approve or Reject -- for example in a Slack message -- and the resume URL carries the decision back into the workflow, so nothing is published until a human approves it.

When should you add a human review step?

For high-stakes outputs -- legal documents, customer-facing communications, financial summaries -- always route AI-generated content through human review before publishing. n8n's Wait node combined with webhook callbacks makes this pattern straightforward.

Real-world example: A workflow generates a weekly customer newsletter using AI, then sends it to a marketing manager via Slack for approval before scheduling it in the email platform.

How do you build a human-in-the-loop approval flow in n8n?

Workflow structure:

[Schedule Trigger] → [Gather Data] → [Generate Newsletter: GPT-4o]
    → [Send to Slack for Review] → [Wait for Approval]
    → [IF: approved] →── yes ──→ [Schedule in Mailchimp]
                      └── no ──→ [Regenerate with Feedback]

Slack message with approval buttons (using Slack Block Kit):

{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Weekly Newsletter Draft*\nGenerated: {{ $now.toFormat('dd MMM yyyy') }}"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "{{ $json.newsletter_preview }}"
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "Approve" },
          "style": "primary",
          "action_id": "approve",
          "url": "{{ $execution.resumeUrl }}/approve"
        },
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "Reject with Notes" },
          "style": "danger",
          "action_id": "reject",
          "url": "{{ $execution.resumeUrl }}/reject"
        }
      ]
    }
  ]
}

The Wait node pauses execution until the reviewer clicks a button. The resume URL carries the decision:

// Code node after Wait — process the reviewer's decision
const webhookData = $json;

if (webhookData.path === 'approve') {
  return [{ json: { approved: true, newsletter: $('Generate Newsletter').first().json.content } }];
} else {
  return [{ json: { approved: false, feedback: webhookData.body?.notes || 'No specific feedback' } }];
}

How do you keep the workflow from waiting forever?

Tip: Set a Deadline. Configure the Wait node with a Limit Wait Time of 24 hours. If no one reviews in time, route to a fallback (send a reminder, or skip that week's newsletter). Never let a workflow wait indefinitely.

Human-in-the-loop adds a few hours of latency but prevents AI hallucinations from reaching your customers.

Related: Use Manual Trigger During Development Instead of Webhook or Schedule · Flatten Deeply Nested API Responses

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

190 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book an introductory call