BLOG

How I build error branches for n8n workflows

By Nguyễn Hải Minh · Published · 4 min read

An error branch in n8n has three jobs: notice that a node failed, work out what kind of failure it was, and tell a person something they can act on. I sort failures by HTTP status and by the response body, because n8n's own message sometimes gives two different causes the same wording. Only temporary errors get retried, and nothing is rerun against a client's real data when the write can't be undone.

The failure that made me write this down

I keep a small n8n template: a webhook comes in, the data is validated, a row gets written. While testing it I made one node fail on purpose. The webhook still answered 200 with an empty body, {}. Whoever called it had no way of knowing the row was never written. That template still has no error branch, and I won't hand it to a client until it has one.

Seven failures, recorded on a local n8n

To see what an error branch will actually receive, I built seven tiny workflows, each a manual trigger plus one HTTP Request node calling a mock server on my machine that returns one kind of failure. I ran them on n8n 2.20.0 locally on 10 November 2026 and copied the messages as they appeared:

  • 401, missing credentials: "Authorization failed - please check your credentials"
  • 401, expired token: the same message
  • 403: "Forbidden - perhaps check your credentials?"
  • 429: "The service is receiving too many requests from you"
  • 200 with broken JSON: "Invalid JSON in response body"
  • 503: "Service unavailable - try again later or consider setting this node to retry automatically (in the node settings)"
  • 404: "The resource you are requesting could not be found"

The first two lines are the useful part. A missing credential and an expired token produce identical text, so a branch that only reads the message will send someone to fix the wrong thing. In my mock, the expired case carries token_expired in the response body, so the branch has to read the body.

These runs show what n8n 2.20.0 reports for each status code. They don't show how a real API or OAuth flow behaves, and I haven't run them on Zapier or Make.

What goes into the branch

I keep a small Python function, classify(status, headers, body), that turns a status code and body into a likely cause and a next step. It has nine unit tests against the mock server. The order of the checks matters:

if status == 429 or (status >= 400 and "rate" in low and "limit" in low):
    ...  # rate_limit: read Retry-After
if status == 401 and "expired" in low:
    ...  # token_expired, checked before the generic 401

*Simplified excerpt. The real function also accepts token_expired in the body.*

Rate limiting is checked first, so a 4xx whose body talks about a rate limit is handled as one. In n8n, the branch ends up doing roughly this:

  1. Catch the failure with an Error Trigger in an error workflow, so every workflow reports problems the same way.
  2. Record the exact message, the node that failed and the time. I don't guess at the cause before I've read these.
  3. Sort by cause. A 429 or 5xx gets a limited Retry On Fail with a wait, using Retry-After when the service sends it. A 401, 403 or 404 is not retried, since trying again won't fix a credential, a missing scope or a wrong ID.
  4. Send an alert to someone who will read it, with the cause and the suggested step.
  5. If a webhook started the workflow, return a real error code to the caller instead of an empty 200.

The classifier only gives a hint. It matches codes and keywords, so I still open the response body.

What I haven't tested yet

My checklist has five more items: input fields that are missing or the wrong type, webhooks that work with the test URL and return 404 in production, timeouts, silent failures other than the one above, and records written twice. Those come from general experience, and I have no test flows or recorded messages for them yet.

If you have an n8n workflow that fails quietly or in ways you can't explain, send me what it does, how it runs today and the last error message you saw. That is usually enough for me to say where I would start.