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 bef...

TipIntermediate2 min read

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.

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]
```text
Slack message with approval buttons (using Slack Block Kit):

```json
{
  "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"
        }
      ]
    }
  ]
}
```text
The **Wait** node pauses execution until the reviewer clicks a button. The resume URL carries the decision:

```javascript
// 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' } }];
}
```text
> **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](../api-cost-optimization/01-use-manual-trigger-during-development-instead-of-webhook-or-schedule.md) | [Flatten Deeply Nested API Responses](../code-node-mastery/01-flatten-deeply-nested-api-responses.md)

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.