n8n logo
No-code

n8n Integration

Pause n8n workflows and require human approval before continuing.

n8n is a visual workflow automation tool. This guide walks you through every node, every field, and every click needed to add human approval to any n8n workflow — no coding required.

How it works

The overall pattern:

  1. An AI node (or any node) generates content — e.g. an email draft
  2. A Code node parses the AI output into clean fields your later nodes can reference
  3. An HTTP Request node sends the decision to The Handover and gets back a decision id
  4. A Wait node pauses the workflow
  5. Another HTTP Request node checks whether a decision has been made
  6. An IF node branches: loop back to wait if still pending, or continue if resolved
  7. A final IF node routes to your approved or denied path
  8. The action node (Gmail, Slack, etc.) references the parsed fields by node name

Step 1: Create a Header Auth credential

You need to store your API key once so every HTTP Request node can use it securely.

  1. In the n8n sidebar, go to Settings → Credentials
  2. Click + Add credential
  3. Search for and select Header Auth
  4. Fill in the two fields:
    • Name: Authorization
    • Value: Bearer ho_your_api_key_here
  5. Click Save and give it a name like The Handover

Where to find your API key: Log in to your dashboard, go to API Keys, and copy your key. It starts with ho_.

You can also use the cURL import shortcut — open any HTTP Request node, click the Import cURL button at the top, and paste this (replace the key):

curl -X POST https://thehandover.xyz/decisions   -H "Authorization: Bearer ho_your_key"   -H "Content-Type: application/json"   -d '{"action":"Test","approver":"you@example.com","urgency":"medium"}'

n8n will pre-fill the method, URL, headers, and body automatically.

Step 2: Parse your AI node's output (if using an AI agent)

AI nodes in n8n return their output as a raw string — often wrapped in markdown code fences like ```json ... ```. This means you can only reference {{ $json.output }}, not individual fields like {{ $json.subject }}.

To fix this, add a Code node directly after your AI node:

  1. Add a Code node and set the language to JavaScript
  2. Paste this code:
const raw = $input.first().json.output;
const cleaned = raw.replace(/```jsons*/g, '').replace(/```/g, '').trim();
const parsed = JSON.parse(cleaned);
return [{ json: parsed }];

After this node, {{ $json.receiver }}, {{ $json.subject }}, and {{ $json.message }} (or whatever fields your AI returns) are all available.

Important: Later nodes like Gmail run after the polling loop, so $json at that point refers to the IF node's output, not the parsed email. Always reference the Parse node explicitly by name: $('Parse Email Output').first().json.subject — replace Parse Email Output with whatever you named the Code node.

Step 3: Add the "Create Decision" node

Add an HTTP Request node to your workflow at the point where you need approval. Configure it as follows:

  • Method: POST
  • URL: https://thehandover.xyz/decisions
  • Authentication: Generic Credential TypeHeader Auth → select the credential you created in Step 1
  • Body Content Type: JSON
  • Specify Body: Using JSON
  • JSON body:
{
  "action":   "{{ $json.action_description }}",
  "context":  "{{ $json.context }}",
  "approver": "approver@yourcompany.com",
  "urgency":  "high",
  "timeout_minutes": 60
}

Replace {{ $json.action_description }} and {{ $json.context }} with expressions that reference data from earlier nodes, or hard-code plain text strings. Replace approver@yourcompany.com with the email of the person who should approve.

The response will look like:

{
  "id": "abc-123",
  "status": "pending",
  "expires_at": "2024-01-01T13:00:00Z"
}

The id is what you'll use to check the result. n8n automatically makes this available as {{ $json.id }} in the next node.

Step 4: Add a Wait node

Add a Wait node directly after the Create Decision node. This pauses the workflow while the approver reviews.

  • Resume: After Time Interval
  • Wait Amount: 1
  • Wait Unit: Minutes

This means n8n will check for a result every minute. You can set a longer interval if your approvers typically take more time — just make sure it's shorter than your timeout_minutes.

Step 5: Add the "Check Decision" node

After the Wait node, add another HTTP Request node to check the decision status:

  • Method: GET
  • URL: https://thehandover.xyz/decisions/{{ $('Create Decision').first().json.id }}
  • Authentication: Same Header Auth credential as before

The expression {{ $('Create Decision').first().json.id }} reaches back to the first node (replace Create Decision with whatever you named that node) and pulls out the id from its output.

Step 6: Add an IF node — "Still pending?"

Add an IF node after the Check Decision node to decide whether to wait again or move on:

  • Condition 1:
    • Value 1: {{ $json.status }}
    • Operation: is equal to
    • Value 2: pending

Connect the true output back to the Wait node (creating the polling loop). Connect the false output to the next step.

Step 7: Add an IF node — "Approved or denied?"

Add a second IF node on the false path from Step 6:

  • Condition 1:
    • Value 1: {{ $json.status }}
    • Operation: is equal to
    • Value 2: approved

Connect the outputs to whatever should happen next:

  • true (approved) → continue your workflow
  • false (denied or modified) → send a notification, log the result, or stop

Accessing the approver's notes: If you allow modified responses, the approver's feedback is in {{ $json.response_notes }}. You can use this in a follow-up node to adjust what the workflow does next.

Faster alternative: Webhook callback

Instead of polling every minute, you can have The Handover call your n8n workflow the instant a decision is made. This is faster and uses fewer executions.

  1. Add a Webhook node to your workflow and copy its URL
  2. In your Create Decision HTTP Request body, add: "callback_url": "https://your-n8n.example.com/webhook/abc123"
  3. The Handover will POST the resolved decision to that URL the moment the approver clicks — no polling needed

The webhook payload includes id, status, response_notes, and all original decision fields.

Minimal working example

This is a complete walkthrough of a real workflow: an AI agent generates a personalised email, a human approves it, and then Gmail sends it. Every screenshot shows the exact settings used.

Complete workflow

The final layout in n8n — nodes connected left to right, with the polling loop visible between Wait and IF 1.

Complete n8n workflow overview

Node 1 — Generate Email (AI Agent)

The AI agent is prompted to produce a JSON object with receiver, subject, and message fields. The raw output is a string — that's handled by the next node.

Generate Email AI agent node settings

Node 2 — Parse Email Output (Code node)

Strips the markdown code fences and parses the JSON string into a proper object. After this node, $json.receiver, $json.subject, and $json.message are all available — but reference them by node name later in the workflow.

Parse Email Output Code node settings

Node 3 — Create Decision (HTTP Request)

POST to /decisions with the action description and approver email. The action field references the parsed subject so the approver sees exactly what they're approving.

Create Decision HTTP Request node settings

Node 4 — Wait

Pauses for 1 minute before checking. The true output of IF 1 loops back here, creating the polling cycle.

Wait node settings

Node 5 — Check Decision (HTTP Request)

GET the decision using the ID saved from Node 3. Note the expression reaching back to Create Decision by node name.

Check Decision HTTP Request node settings

Node 6 — IF 1: Still pending?

Checks if status is pending. True loops back to the Wait node. False means a decision has been made — continue to IF 2.

IF node 1 - still pending check settings

Node 7 — IF 2: Approved?

Checks if status is approved. True goes to Gmail. False goes to a log or notification node.

IF node 2 - approved check settings

Node 8 — Send Email (Gmail)

References the Parse node by name to get the email fields. If you used $json.subject here it would fail — you must use $('Parse Email Output').first().json.subject because $json at this point is the IF 2 output.

Send Email Gmail node settings

Common mistakes

  • Referencing $json.field after the polling loop — by the time Gmail (or any final node) runs, $json is the IF node's output, not the parsed email. Always reach back to the Parse node explicitly: $('Parse Email Output').first().json.subject.
  • AI output not parsed — if your AI agent wraps output in ```json fences, trying to use $json.subject directly will fail with undefined. Add the Code node from Step 2 to strip and parse first.
  • Expression references the wrong node name — the name in $('Node Name') must match exactly, including capitalisation. Click the node and check the name in the top-left of its panel.
  • Decision ID gets lost in the loop — n8n only carries the output of the most recent node into the next. Reference the Create Decision node by name ($('Create Decision').first().json.id) rather than $json.id once you're past the first node.
  • Wait node set too short — intervals under 65 seconds keep the workflow in memory. If your n8n instance restarts during that window, the execution is lost. Use at least 2 minutes for robustness.
  • Credential not linked — setting Authentication to "Header Auth" without selecting the saved credential from the dropdown will send requests without auth and get a 401 error.

Common use cases

  • Approve AI-generated emails or messages before they're sent
  • Confirm invoice or payment processing
  • Review CRM record updates or deletions
  • Gate outbound API calls to billing, HR, or ERP systems
  • Human sign-off on automated social media posts

Ready to add human oversight?

Free to start. No credit card required. Five minutes to integrate.

Get Started Free