n8n Error Workflows Operators Actually Read (Not Just Slack Noise)
Wire one n8n Error Trigger handler with a real alert contract: severity, owner, execution link, and mute rules — then attach it to every production flow.
An n8n error workflow people act on is a shared Error Trigger handler with a fixed alert contract — severity, owner, execution link, failed node, and next action — attached to every production workflow. A bare “post to Slack” node is how channels get muted.
Spurlock Studios treats the error handler as product surface, not an afterthought. Principles live in the Production n8n handbook; this spoke is the implementable contract.
The short answer
- One shared handler for production flows; set it under each workflow’s Settings → Error workflow.
- Alert contract beats clever copy — fields first, prose second.
- Manual Execute does not fire the Error Trigger; test with an activated automatic path or mocked payload.
- Continue on Fail is for controlled branches, not a substitute for the error workflow.
- DLQ stores the work; the error workflow wakes a human — use both (dead letter queues).
What an Error Trigger workflow is for
Per current n8n docs, the Error Trigger receives details when a linked workflow fails and runs your handler. Typical payload fields include:
| Field path | Use in the alert |
|---|---|
workflow.name / workflow.id | Which flow broke |
execution.id / execution.url | Deep link for the operator |
execution.error.message | What failed |
execution.lastNodeExecuted | Where to look first |
execution.retryOf | Present only on retries |
execution.mode | Context (automatic vs other) |
Caveats from the docs that matter in production:
execution.idandexecution.urlrequire the execution to be saved; they may be missing if the trigger node itself failed.- Trigger-node failures use a different shape (
trigger.error…) with lessexecution{}data — your alert template must tolerate missing links. - A workflow that contains an Error Trigger uses itself as its error workflow by default.
- You do not have to publish the error-handler workflow for it to run when selected as an Error workflow.
The alert contract (minimum fields)
Every P1/P2 alert must include:
severity: P1 | P2 | P3
workflowName
workflowId
executionUrl (or "unavailable — trigger failure")
failedNode
errorMessage (trimmed)
ownerPrimary
ownerBackup
customerOrRecordId (if known)
idempotencyKey (if any)
dlqStatus: written | skipped | n/a
nextAction: pause | replay | wait-retry | ignore-enrichment
occurredAt
If a field is unknown, write unknown — do not omit the line. Operators scan for missing structure faster than they read paragraphs.
Severity and who gets paged
Reuse the overnight posture from automation fails overnight:
| Severity | Route | Example |
|---|---|---|
| P1 | Pager / SMS + never-mute channel | Payment, CRM overwrite, customer message |
| P2 | Morning triage channel | Lead sync lag, reporting job |
| P3 | Weekly board | Optional enrichment skip |
Map severity inside the error workflow with a simple table on workflow name or tag — do not make humans infer it from the error string at 2am.
Wire once, attach everywhere
Procedure:
- Create workflow
Error Handler — Productionwith Error Trigger first. - Build: normalize payload → classify severity → write DLQ row → send alert → (optional) acknowledge thread.
- Save. Confirm it appears in the Error workflow dropdown.
- For each production workflow: Options → Settings → Error workflow → Error Handler — Production → Save.
- Keep a checklist of attachments; new workflows do not inherit this by magic.
Attachment checklist:
- Money / billing paths
- CRM create/update paths
- Customer messaging paths
- Nightly reconciliation crons
- Webhook receivers that acknowledge early then process
Staging can share a quieter handler that never pages phones.
Testing without trusting a green checkbox
n8n’s documented rule: you cannot test error workflows when running workflows manually. The Error Trigger only runs when an automatic workflow errors.
Practical test ladder:
- Mock path: Temporarily put a Set/Edit Fields node with sample Error Trigger JSON in front of your alert/DLQ nodes; execute the handler workflow to prove formatting.
- Activated failure: Activate a throwaway workflow that uses Schedule or Webhook, point its Error workflow at your handler, force a failure (bad URL, Stop and Error), invoke it automatically (not Manual Execute).
- Trigger-failure case: Break a webhook/cron activation path once and confirm your template survives missing
execution.url. - Mute drill: Fire five identical errors; confirm collapse / dedupe still leaves one actionable message.
If you only clicked Execute in the editor, you have not tested the Error Trigger.
Continue on Fail vs Error Workflow
| Mechanism | Use when | Avoid when |
|---|---|---|
| Error Workflow | The run should fail and a human/DLQ path must run | You want the item to continue downstream |
| Continue on Fail | A specific node may fail and a branch handles it | You enable it globally to “keep going” |
| Retry on Fail | Transient network / 429 with budget | Validation or auth failures |
| Stop and Error | You want a controlled failure message into the Error Trigger | Debugging only in manual mode and expecting the handler to fire |
Continue on Fail without a branch that dead-letters or skips intentionally swallows API errors. That is how silent corruption starts.
Mute prevention rules
Alert quality dies when volume is undifferentiated. Enforce:
- No enrichment skips on P1 channels.
- Collapse duplicates: same
workflowId+failedNode+ normalized message within 30–60 minutes → update count, do not spam new threads. - Separate channels for P1 vs triage.
- Never
@channelon P3. - Include nextAction so the first responder knows whether to pause or wait.
If the handler is noisier than the failures, operators will mute the handler — not fix the workflows.
Where DLQ fits next to alerts
| Concern | Owner |
|---|---|
| Wake a human with context | Error workflow alert |
| Store payload + error for replay | DLQ table / queue |
| Prevent duplicate side effects on replay | Idempotency keys |
| Decide retry vs park | Failure classification |
Alerts without DLQ create panic. DLQ without alerts creates a quiet pile. Build the pair; do not re-litigate DLQ theory here — use the DLQ post.
Normalize before you alert
Error Trigger payloads differ for mid-workflow failures vs trigger-node failures. Normalize early in the handler:
- Read
workflow.name(always try) - Prefer
execution.url; if missing, say so explicitly - Prefer
execution.lastNodeExecuted; fall back totrigger.error.node.name - Prefer
execution.error.message; fall back totrigger.error.message - Set
executionUrlAvailable: true|falsefor the template
Operators should never see a broken Slack message because a field was undefined. Broken templates train people to ignore the channel.
Ownership fields belong in the alert body
Do not rely on “check the wiki.” Put ownerPrimary and ownerBackup in every alert, sourced from:
- A static map in the error workflow (workflow id → owners), or
- A small lookup table, or
- Tags / naming convention you parse carefully
Static maps drift; revisit when people change roles. A wrong owner is still better than no owner — wrong gets corrected; empty gets ignored.
Stop and Error for controlled fails
Use the Stop and Error node when you want a deliberate failure with a clear message into the Error Trigger (for example, validator failed and you refuse to continue). That is cleaner than letting a later node throw a cryptic stack.
Still remember: Stop and Error during a manual Execute will not exercise the Error Trigger. Prove it on an activated automatic path.
Failure mode: “we set up Slack” theater
What breaks: one Error Workflow posts raw JSON to #general. After a rate-limit weekend, the channel is muted. A billing workflow fails on Monday; nobody sees it until a customer asks.
What it costs: missed collections, manual invoice repair, and a team that no longer trusts automation alerts.
What you do instead: ship the contract above, attach the handler to every production flow, prove an automatic failure once, and protect the P1 channel like a pager.
Production attach runbook (copy/paste)
1. Handler workflow saved and named
2. Alert template includes all contract fields
3. DLQ write step verified with mock payload
4. Severity routing table filled for this app domain
5. Each production workflow Settings → Error workflow set
6. Automatic failure test passed (not manual Execute)
7. Trigger-node missing-url case rendered safely
8. Duplicate collapse verified
9. Owners + backups named in the alert body
10. Link to pause steps in the team runbook
Skip step 6 and you are shipping hope.
FAQ
Why didn’t my error workflow fire on a manual run?
Because n8n does not run the Error Trigger on manual executions. Per n8n docs, it only runs when an automatic workflow errors. Activate a test workflow and fail it via webhook/schedule, or mock the payload inside the handler to test formatting.
Should every workflow share one handler?
Share one production handler for consistent alert shape and DLQ writes. Use a separate quiet handler for staging. Special-case only when a domain truly needs a different pager route — still keep the same field contract.
How do I log failures for replay?
In the error workflow, write the original input (when available), error, execution id, and workflow id to your DLQ store before or as you alert. Replay from that record with idempotency checks — details in the DLQ guide.
Continue on Fail vs Error Workflow — which when?
Use the Error Workflow when the execution should fail closed and notify. Use Continue on Fail only on a specific node with an explicit branch that skips, repairs, or dead-letters. Do not enable Continue on Fail to hide errors.
How do I avoid paging for enrichment skips?
Classify enrichment workflows as P3 or handle skips inside the main flow with Continue on Fail + a non-pager log. Only money, CRM integrity, and customer-contact failures belong on the phone.
Where does DLQ fit next to alerts?
DLQ holds failed work for repair and replay. The error workflow notifies humans and should confirm the DLQ write. Alert without storage is a screenshot culture; storage without alert is a forgotten queue.
CTA
If your Error Trigger only dumps JSON into Slack, you built a mute button. Ship the contract, attach it everywhere, and prove an automatic failure once.
Review the spine in the handbook, then use automation or book a call.