n8n error handling: catch failed workflows and get a fix
Build one workflow starting with the Error Trigger and set it as the Error Workflow on everything else. Then add the two things the docs leave out: suppress repeat failures inside a cooldown window so a broken workflow sends one email and not sixty, and send the raw error anyway when the diagnosis step fails.
Setting up an error workflow takes about four minutes and the n8n docs cover it well. The part they skip is what happens after: most error workflows get muted within a couple of weeks, because of how much they send and how little of it you can act on.
How do you catch a failed workflow in n8n?
One workflow catches failures for all of them. Create a new workflow whose first node is the Error Trigger. Then open every other workflow, go to Settings, and set Error Workflow to the one you just built.
That setting is per workflow, not global. This is the single most common reason an error workflow appears to do nothing: it was built correctly and then never attached to anything.
Publish the error workflow itself. If it sits unpublished, n8n skips it and tells nobody. The only trace is a line in the server log:
Calling Error Workflow for "abc123".
Workflow "xyz789" is not active and cannot be executedNo alert, and no alert about the missing alert. Worth checking first when a correctly built error workflow appears dead.
It also will not fire on a manual run. Error workflows are for production executions, so testing by clicking execute on the canvas proves nothing. Publish both, then let a real trigger fail.
Two other cases will not reach it either. A node with continue-on-fail enabled is treated as a successful flagged output rather than a workflow error, so nothing fires. And a workflow that never started, for example a trigger that could not connect, sends a different payload shape, covered below.
The payload has two shapes, and most error workflows only handle one
When a node fails mid-run, the error is under execution.error:
{
"workflow": { "id": "wf-77", "name": "Daily invoice sync" },
"execution": {
"id": "e-901",
"url": "https://your-n8n/execution/901",
"lastNodeExecuted": "Append to Sheet",
"error": {
"name": "NodeApiError",
"message": "Request failed with status code 401",
"node": { "name": "Append to Sheet" },
"stack": "..."
}
}
}When a trigger itself fails, there is no execution to speak of and the error arrives under trigger.error instead:
{
"workflow": { "id": "wf-12", "name": "Webhook intake" },
"execution": { "id": "e-13", "mode": "trigger" },
"trigger": { "error": { "name": "TriggerCloseError", "message": "IMAP connection dropped" } }
}Read one and not the other and you get an alert that says a workflow failed with no message and no node, which is exactly when you most need the detail. Read both, and fall back to execution.lastNodeExecuted when error.node is missing.
Why do people switch error alerts off?
Not because the alerts are wrong. Because there are too many of them. A workflow on a one minute schedule with an expired credential fails sixty times an hour, and sends sixty identical emails. After the second incident you filter the sender, and now you have monitoring you never read while still believing you are covered.
The fix is suppression, and it is a handful of lines. Build a key from the workflow, the failing node and the first stretch of the error message. Store the first time you saw it. If the same key comes back inside your cooldown window, increment a counter and do not send.
const store = $getWorkflowStaticData('global');
store.seen = store.seen || {};
const key = [workflowId, nodeName, message.slice(0, 120)].join('::');
const now = Date.now();
const cooldownMs = 30 * 60000;
const prev = store.seen[key];
let shouldAlert = true;
if (prev && now - prev.first < cooldownMs) {
prev.count = (prev.count || 1) + 1; // count it, do not send it
shouldAlert = false;
} else {
store.seen[key] = { first: now, count: 1 };
}
// and prune, or the store grows forever
for (const k of Object.keys(store.seen)) {
if (now - store.seen[k].first > cooldownMs * 20) delete store.seen[k];
}Two details matter here. Pass the repeat count into the alert, so when you do get an email it says this has now happened nine times rather than pretending it is the first. And note that n8n only persists workflow static data on production executions, so the cooldown will appear to do nothing when you test manually from the canvas. Verify it on a real failure.
Make the alert readable by whoever is on call
A stack trace tells you where the code gave up. It does not tell you that a Google Sheets credential was revoked and needs reconnecting. At seven in the morning you want the second one.
Passing the failure to an AI step to write that sentence works well, with three constraints that are not optional:
- Ban invention. Tell it never to name a node, field or credential that is not in the input, and to say the cause is unclear and give the next diagnostic step instead of guessing. Someone will go and try an invented fix, which wastes more time than giving no fix at all.
- Define transient precisely. A timeout, a 429 or a 5xx from an upstream API usually clears on a retry. Credential and permission errors do not. Have the model classify it so you can route the transient ones somewhere quieter.
- Never let it swallow the alert. If the model returns junk, half an answer, or is rate limited, send the raw error anyway. This is the rule people skip, and skipping it is how you end up trusting a system that has quietly stopped working.
Order the email so the useful part is first:
[HIGH] Daily invoice sync failed at Append to Sheet
The Google Sheets credential on the Append to Sheet node is no longer valid.
WHAT FAILED
Workflow: Daily invoice sync
Node: Append to Sheet
Error: NodeApiError: Request failed with status code 401
Repeats: 9 times in the last 30 minutes
LIKELY CAUSE
The OAuth token was revoked or expired.
WHAT TO DO
1. Open the Append to Sheet node
2. Reconnect the Google Sheets credential
3. Re-run the failed execution
OPEN THE FAILED RUN
https://your-n8n/execution/901
STACK
...What breaks in practice, and what to do about it
Four failure classes cover almost everything, and they want different handling. Lumping them together is why a single alert channel stops being useful.
Expired and revoked credentials
The problem. The most common real failure and the most expensive, because it is silent and total. An OAuth token is revoked, and every run after that fails identically until someone notices. This is where the week-long outages come from.
What to do. Treat it as the highest severity even though nothing crashed. It will not fix itself, it affects every run, and the fix takes two minutes once you know. This is the case the whole workflow exists for.
Rate limits and upstream wobbles
The problem. A 429 or a 5xx that would have worked if it ran again a minute later. Alert on these the same way and the noise trains you to ignore the credential alert above.
What to do. Classify them as transient and route them somewhere quiet, or drop them entirely. Better still, set retry on the node itself so they never reach the error workflow. There is nothing to do with the alert when it arrives.
Schema and shape changes
The problem. An API adds a field, renames one, or starts returning null where it used to return an array. Nothing errors loudly. A downstream node reads undefined and the workflow either fails oddly or, worse, succeeds with wrong data.
What to do. These produce the least readable errors, so this is where a plain-English diagnosis earns its keep. It is also the class most worth logging over time, because the same integration breaking twice in a month is telling you something.
Your own expression and logic errors
The problem. A renamed node breaks a $node['Old Name'] reference somewhere else. Cannot read property of undefined. Almost always introduced by an edit, and almost always found by a customer rather than by you.
What to do. Easy to fix, awkward to find out about late. All you want here is speed, so the alert should name the node and link straight to the failed run.
Who feels this most
Agencies running client automations. The outage is bad, being told about it by the client is worse. One error workflow across every client account means you get there first.
Solo operators with unattended jobs. Nightly syncs and scheduled reports are exactly the things nobody watches. A daily digest of failures is often more useful here than an instant alert.
Anyone whose workflows touch money or customers. A silently failing dunning sequence or lead router costs real revenue per day of silence, and those are precisely the workflows that run unattended by design.
The whole thing is nine nodes
Error Trigger
-> Set config (who to alert, cooldown in minutes)
-> Code: read the failure, decide if it is a repeat
-> IF shouldAlert
true -> Code: build the diagnosis request
-> HTTP: diagnose
-> Code: write the alert
-> Gmail: send
false -> (nothing, the repeat dies quietly)The false branch of that IF going nowhere is deliberate. A suppressed repeat should leave no trace beyond its counter.
Everything above is in a free n8n template you can import and point at your own inbox. It handles both payload shapes, suppresses repeats, and falls back to the raw error when the diagnosis step fails.
n8n error handling questions, straight answers.
How do I get notified when an n8n workflow fails?
Build one workflow that starts with the Error Trigger node, then open Settings on every other workflow and set Error Workflow to it. One error workflow can serve every workflow you own, so this is a single build and then a one-line setting per workflow.
Why do people turn off n8n error alerts?
Because of alert storms. A workflow that runs every minute and fails every minute sends sixty emails an hour, and after the second incident nobody reads any of them. The fix is to suppress repeats: record each unique failure, count how many times it recurs inside a cooldown window, and send once. Suppression is what makes the alert trustworthy, not the alert itself.
What does the Error Trigger actually give you?
The failed execution's data: which node failed, the error name and message, the execution mode, and usually a stack trace and a link to the failed run. Node failures arrive under execution.error. Trigger failures arrive under trigger.error instead, which is the shape most error workflows forget to handle and the reason some failures appear to send an empty alert.
Why does my error workflow not fire?
Four causes, and the first one catches nearly everyone. The error workflow itself must be published. If it is not, n8n skips it silently and the only trace is a log line reading "is not active and cannot be executed", so you get no alert and no error about the missing alert. After that: the failing workflow has no Error Workflow set in its own Settings, which is per workflow and not global; or the failing node has continue-on-fail enabled, which n8n treats as a successful flagged output rather than a workflow error; or the workflow never started at all, in which case the payload arrives in the trigger.error shape.
Does the error workflow fire on a manual test run?
No, and this trips people up when they try to test it. Error workflows run for production executions. Running the failing workflow by hand from the canvas, or through the CLI, will not call it. To test it properly, publish both workflows and let a real trigger fire, for example a schedule set to a short interval that throws on purpose. Deactivate that test afterwards.
Should the alert include the stack trace?
Include it, but not at the top. The stack trace is the thing you need on the rare occasion the summary is wrong, and it is noise on every other occasion. Put a plain-English cause and the fix first, the failing node and a link to the run second, and the trimmed stack at the bottom for when you actually need it.
Is it safe to have AI diagnose the error?
Only if it cannot go silent. An AI diagnosis is a summary layer, so the raw error must still be sent when the model returns nothing usable, returns half an answer, or is rate limited. A diagnostic tool that goes quiet when the diagnosis fails is worse than no tool, because you now believe you are covered.
How do I stop the AI inventing a fix?
Constrain it in the prompt. Tell it never to name a node, a field or a credential that does not appear in the input, and to say the cause is unclear and give the next diagnostic step rather than guess. An invented fix costs more than no fix, because someone will go and try it.
Does the cooldown survive an n8n restart?
It depends where you store it. Workflow static data persists across executions, but n8n only writes it on production executions, not on manual test runs. That means the suppression logic works when a real failure fires the error workflow, and appears to do nothing when you test it manually from the canvas. Verify it on a real failure before trusting it.
Tired of finding out from the client?
30 minutes, free. Bring the workflows you rely on. You leave with a plan for error handling that you will actually keep switched on, whether you build it or we do.
Keep reading.
Win back the churn nobody chose
Stripe retries the card but sends one generic notice. Branch the email on attempt number, keep the amount and payment link exact, and get told before a subscription is written off.
See every thread waiting on them
Has this thread had a reply is the wrong test and it hides your best leads. Compare sent against received per thread, then draft the nudge instead of sending it.
Know which ad to scale, and by how much
Spend and result floors, a margin over the account average, frequency headroom and budget cap checks, then a sized raise.
Rather have it built for you?
Skip the build. A free 30-minute call and we set this up in your stack, live in a week or two.