n8n human in the loop approvals prevent an AI draft—or any automation—from turning into a consequential action without a fresh, traceable decision. It works for sending email, posting externally, changing customer data, closing tickets, issuing refunds, or publishing content.

The central idea is simple: prepare the exact action first, store what is being approved, wait, verify the decision, re-check the target, then execute exactly once. An “Approve” button by itself is not a control.

← Return to n8n AI agent components and design

The approval lifecycle

Safe n8n human approval lifecycle from prepared payload to one-time execution
Approval applies to one stored payload, one target, one reviewer decision, and one limited time window.

The reusable workflow has these stages:

  1. Prepare the exact proposed action.
  2. Create an approval ID and payload hash.
  3. Store a pending approval record.
  4. Notify an authorized reviewer with context.
  5. Wait for Approve, Edit, Reject, or timeout.
  6. Verify reviewer identity, expiry, status, payload hash, and target state.
  7. Execute the approved action once.
  8. Store the final result and audit timestamps.

Step 1: prepare a complete action payload

Do not ask someone to approve a vague intent such as “reply to the customer.” Build the exact proposed action first.

For a support reply, use an Edit Fields node named Prepare Action:

{
  "actionType": "send_support_reply",
  "targetId": "T-8421",
  "recipient": "asha@example.com",
  "subject": "Re: Invoice question",
  "body": "Your invoice changed because…",
  "sourceIds": ["KB-14", "KB-22"],
  "targetVersion": "2026-09-21T09:05:00Z"
}

Keep this payload stable after the request is created. If a reviewer edits the body, create a new payload version and hash, then record that edited version as the approved text.

Step 2: create an approval record

Create a unique approvalId. Use the Crypto node or a reviewed Code node to hash a stable serialized representation of the proposed payload with SHA-256. The hash proves whether the payload changed between request and execution; it is not a replacement for access control.

Store a record in PostgreSQL, an n8n Data Table, or another durable store:

{
  "approvalId": "APR-2026-08421",
  "actionType": "send_support_reply",
  "targetId": "T-8421",
  "payload": { "body": "Your invoice changed because…" },
  "payloadHash": "sha256:7d95…a41b",
  "status": "pending",
  "requestedAt": "2026-09-21T09:30:00Z",
  "expiresAt": "2026-09-21T10:00:00Z",
  "requestedByWorkflow": "2159",
  "requestedByExecution": "991204",
  "reviewerId": null,
  "decidedAt": null,
  "executedAt": null
}
n8n human approval record with payload hash status reviewer and expiry
The record links the proposed action, reviewer decision, expiry, and final execution. It also prevents an old approval from being reused.

Add a unique constraint on approvalId. When practical, also prevent two live approval requests for the same action type, target, and target version.

Step 3: send the reviewer enough context

Use Slack, Teams, email, an internal portal, or a help-desk task. The reviewer should see:

  • what action will happen;
  • which account, ticket, message, or record it affects;
  • the exact proposed content or values;
  • source/evidence used;
  • why review is required;
  • request time and expiry;
  • Approve, Edit, and Reject choices.

Do not send credentials, payment data, excessive personal data, or secret prompt text to the approval channel. Link an authorized reviewer to a protected internal page when the context is sensitive.

Do not put a raw permanent “approve” URL in a public channel. The approval endpoint must authenticate the reviewer or validate a short-lived, single-use token tied to the approval record. Prefer identity-based access so the audit trail can name the reviewer.

Step 4: pause with the Wait node

Add Wait and select the webhook-resume option available in your n8n version. n8n exposes a per-execution resume URL through $execution.resumeUrl. Your protected approval interface can call that URL after it has authenticated and validated the reviewer.

Set a time limit. If the approval expires after 30 minutes, the workflow must route to an expired branch instead of waiting forever or executing an old decision.

The resume payload should contain only controlled decision data such as:

{
  "approvalId": "APR-2026-08421",
  "decision": "approve",
  "reviewerId": "U045NISHI",
  "decisionTimestamp": "2026-09-21T09:42:00Z"
}

Do not trust reviewerId merely because it arrived in JSON. Your approval application or identity-aware gateway must establish it.

Step 5: verify the decision after resume

After Wait resumes, fetch the approval record again. Route to execution only when every check passes:

  • the approval ID exists;
  • status is still pending;
  • decision is exactly approve;
  • the authenticated reviewer is authorized for this action;
  • the decision arrived before expiresAt;
  • the proposed payload hashes to the stored payloadHash;
  • the approval has not already been executed.

For Reject, update status to rejected, store reviewer and time, and end without touching the target. For Edit, store the edited proposal as a new version and re-run the checks required by your policy; do not silently treat arbitrary edited text as the original approved payload.

Step 6: re-check the business target

Approval can become stale. Re-fetch the ticket, document, order, post, or account immediately before executing.

For a support ticket, confirm that:

  • the ticket still exists and belongs to the same customer;
  • it is still open;
  • no person has already replied;
  • its current version matches targetVersion;
  • the proposed recipient is still valid.

If state changed, mark the request stale and create a new review. Approval is permission for a particular action against a particular state—not permission forever.

Step 7: claim and execute once

Before the final action, atomically change status from pending to executing. Only one execution should succeed in that transition. If the record is already executing, executed, rejected, expired, or stale, stop.

Call the final service using an idempotency key where supported. Good keys include approvalId or a combination of action type and target version. Send exactly the stored approved payload.

On success, write:

status = executed
executedAt = {{ $now }}
externalResultId = the message / post / transaction ID

On failure, store execution_failed, the error class, retry count, and whether retry is safe. Do not send a second action merely because the first response timed out; check the external system or idempotency result first.

Where native AI tool approval fits

Current n8n versions may offer human review controls for selected AI tool calls. Use them when the tool-call review UI and channel fit your requirements. Still apply the same design questions:

  • Does the reviewer see the exact arguments?
  • Is reviewer identity known?
  • Does approval expire?
  • Can the payload change after approval?
  • Is the tool idempotent?
  • Is the target revalidated?
  • Is the decision and outcome logged?

For sensitive actions, a deterministic outer workflow with a durable approval record gives you clearer control than relying on the agent alone.

Test matrix

Test Expected result
Authorized reviewer approves before expiry Exact stored payload executes once
Reviewer rejects Status rejected; no target action
Unauthorized user submits approval Decision rejected and logged
Approval arrives after expiry Status expired; no execution
Payload changed after request Hash mismatch; new approval required
Target changed after request Status stale; new review required
Approve called twice Second call cannot claim the record
External API times out after accepting action Check idempotency/result before retry
Workflow restarts while waiting Durable approval record still explains state

Common approval mistakes

Mistake Safer design
Approve button shows only “Proceed?” Display exact target, payload, evidence, reason, and expiry
Approval link is permanent Short-lived single-use request with authenticated reviewer
Draft changes after approval Hash/version the payload and send that exact version
Old approval executes against new state Re-fetch and compare target version
Two clicks create two writes Atomic state transition plus external idempotency key
Audit log stores secrets Log identifiers and outcomes; minimize sensitive payloads

Human approval adds real safety only when the reviewer knows what will happen and the workflow proves that the approved action is the action actually executed.

Official references and next guides

Similar Posts