n8n foutafhandeling
Gebaseerd op czlonkowski/n8n-skills @ 72470a0, licentie MIT
Dit bestand is door ToolBrain vertaald en inhoudelijk gewijzigd op 2026-08-30, op basis van
n8n-error-handling SKILL.md uit czlonkowski/n8n-skills (MIT), zie de bron_url hierboven en
../LICENSES/n8n-skills-MIT.txt.
Standaard geldt in n8n: als één node een fout gooit, stopt de hele workflow. Voor een interactieve run die je zelf zit te volgen is dat prima — je ziet de rode node en fixt hem. Maar voor alles wat onbewaakt draait (een webhook-API, een cronjob, een queue-worker, een agent-tool) is dat de verkeerde default: de aanroeper krijgt een timeout of een lege 500, de beheerder krijgt geen alert, en het symptoom is "de integratie doet het gewoon niet meer" zonder log en zonder aanwijzing.
Deze skill gaat over het luid, gestructureerd en herstelbaar maken van fouten — en idealiter zelfherstellend, zodat tijdelijke haperingen nooit een mens bereiken.
Twee ideeën voorkomen de meeste stille storingen:
- Per-node foutuitgangen — een falende node routeert naar een tweede, door jou gecontroleerde uitgang, in plaats van de run te doden.
- Een workflow-brede foutworkflow — een vangnet dat afvuurt voor alles wat aan de per-node afhandeling ontsnapt (timeouts, crashes tussen nodes, niet-bedrade fouten).
Wanneer heb je dit echt nodig
| Workflowtype | Vereiste foutafhandeling |
|---|---|
| Webhook/API (met "Respond to Webhook") | Verplicht. Elke falende node z'n foutuitgang bedraad; statuscode past bij de oorzaak. |
| Gepland/cron/queue-worker/agent-tool (onbewaakt) | Verplicht. Een workflow-brede foutworkflow, plus retry op netwerknodes. |
| Eenmalige interne run die je zelf bekijkt | Optioneel. De standaard "stop de workflow" is prima — je ziet de rode node en draait opnieuw. |
De scheidslijn: zodra iemand anders dan jijzelf de output ziet — een downstream systeem, een eindgebruiker, een engineer met dienst — moet de fout afgehandeld worden, niet ingeslikt. Ben jij de enige kijker en is "ik merk het en draai opnieuw" acceptabel, dan mag het losser.
De grootste stille valkuil: een foutuitgang zetten is een TWEESTAPS-actie
Dit is dé manier waarop een n8n-workflow fouten lijkt af te handelen terwijl hij ze in werkelijk- heid inslikt. Een falende node naar een handler routeren vraagt twee wijzigingen, en slechts één ervan doen oogt compleet maar gedraagt zich verkeerd:
- Zet
onError: "continueErrorOutput"op de node. Dit creëert de tweede uitgang. Zonder dit bestaatmain[1]niet, hoe je ook bedraadt. - Bedraad die foutuitgang (
connections.<node>.main[1], dussourceIndex: 1) naar een echte handler. Zonder doel verdwijnt de foutdata in het niets.
| Wat je deed | Wat er in productie gebeurt |
|---|---|
onError gezet, foutuitgang niet bedraad | Foutdata wordt stilzwijgend weggegooid. Downstream vuurt niet. Het dashboard toont de run als geslaagd. Ergste geval: nergens een log. |
Foutuitgang bedraad, onError niet gezet | Het slot vuurt nooit; de handler is onbereikbaar. Bij een fout stopt de workflow gewoon (default stopWorkflow). |
| Beide gedaan | Fout routeert via main[1] naar je handler. ✅ |
Geldige onError-waarden: "stopWorkflow" (default, fout stopt de hele workflow),
"continueRegularOutput" (foutitem stroomt via de normale uitgang — zelden juist, want
downstream krijgt dan foutvormige data en gaat gewoon door), "continueErrorOutput" (foutitem
stroomt via de aparte foutuitgang, die jij bedraadt).
Verifieer daarna altijd. Deze valkuil komt niet naar voren in een gewone validatie — een half
bedrade foutuitgang valideert schoon. Haal de workflow op en controleer beide helften: staat
onError echt op "continueErrorOutput", en bevat de bedrading van main[1] echt je handler.
Zelfherstel eerst: retry vóór je foutpaden bedraadt
Bouw eerst zelfherstel, zodat tijdelijke storingen die foutpaden nooit bereiken. Zet op elke node die een netwerkdienst aanroept — HTTP Request, communicatie (Gmail/Slack/Discord), databases, AI-nodes, integraties van derden — retry op node-niveau:
{ type: "updateNode", nodeName: "HTTP Request",
changes: {
retryOnFail: true,
maxTries: 3,
waitBetweenTries: 5000 // ms
} }Waarom dit eerst komt: een 429 of een kortstondige upstream-hapering lost zichzelf meestal op met een retry. De foutuitgang vuurt dan alleen bij echte, aanhoudende storingen — waardoor je 5xx-responses en on-call-alerts de werkelijke problemen weerspiegelen in plaats van ruis.
Engine-limieten om te kennen: retry vuurt op elke fout (geen filter per statuscode), maxTries
plafonneert op 5, en waitBetweenTries plafonneert op 5000 ms — dus 5000 is zowel het maximum
als een verstandige default.
API-workflows: de standaardvorm
Een webhook-getriggerde workflow die antwoordt aan de aanroeper kent één regel die alles overstijgt: geen hangende takken. Elk pad — succes én elke fout — moet eindigen bij een "Respond to Webhook"-node, anders blijft de aanroeper wachten tot de timeout.
Webhook (responseMode: "responseNode")
├── input valideren → verwerken → Respond (200, body)
└── (foutuitgang van elke falende node → sourceIndex 1)
→ Respond (4xx/5xx, gestructureerde foutbody)
→ optioneel: volledige fout privé loggen / notificerenDrie dingen maken dit werkend:
- Fan-in naar één foutresponder. Veel falende nodes kunnen hun
main[1]naar één Respond-node laten samenkomen — houdt de graaf overzichtelijk. - Validatiefouten (4xx) worden vooraf gecheckt, niet via foutuitgangen. Een ontbrekend veld is geen crash — het is een verwachte uitkomst met een bekend antwoord. Vertak hierop met IF/Switch (of een schema-validator) en geef direct 400/401/403/404 terug. Foutuitgangen zijn voor onverwachte storingen (5xx).
responseCodestaat standaard op 200 — zelfs op foutpaden. Dit is zelf een stille valkuil: een foutpad dat 200 met een foutbody teruggeeft, ziet er voor de HTTP-client van de aanroeper uit als succes — hun eigen foutafhandeling vuurt dan nooit. ZetresponseCodeexpliciet op elke Respond-node.
Input-validatie: valideer een gestructureerde payload met één enkele Set-node die een
zelfaangeroepen functie draait, in plaats van een keten van IF/Switch-nodes per veld. Zo'n node
geeft { valid, validationError, details, requiredSchema } terug, en een IF vertakt op valid
naar je logica (200) of een 400-Respond die het schema teruggeeft zodat de aanroeper zichzelf kan
corrigeren.
Responsvormen: koppel oorzaak aan statuscode
Een 5xx met platte tekst "Internal Server Error" is technisch een foutrespons maar praktisch onbruikbaar. En niet elke storing is een 5xx. Koppel de statuscode aan de wérkelijke oorzaak, want de aanroeper vertakt erop: hun monitoring alarmeert op 5xx (jouw schuld) maar niet op 4xx (hun schuld), en 5xx suggereert "opnieuw proberen" terwijl 4xx suggereert "niet doen".
| Oorzaak | Status | Foutcode | Waar afgehandeld |
|---|---|---|---|
| Verplicht veld ontbreekt/verkeerd type | 400 | validation_error | Vooraf (schema-validator/IF) |
| Authenticatie ontbreekt/ongeldig | 401 | unauthorized | Vooraf |
| Geauthenticeerd maar niet toegestaan | 403 | forbidden | Vooraf |
| ID geldig in request, ontbreekt in eigen data | 404 | not_found | Vertakken op het opzoekresultaat |
| Conflicteert met huidige staat (dubbel, race) | 409 | conflict | Detecteren met logica |
| Rate limit overschreden | 429 | rate_limit_exceeded | Retry-After-header zetten |
| Node gooide fout, oorzaak onbekend | 500 | internal_error | Foutuitgang |
| Externe API gaf een fout terug | 502 | upstream_error | Foutuitgang van de HTTP-node |
| Kan nu niet verwerken (downstream down) | 503 | service_unavailable | Specifieke fout detecteren, retry hinten |
| Externe API timede uit | 504 | upstream_timeout | Foutuitgang gefilterd op bericht |
Kortom: 4xx wordt vóór het werk beslist (IF/Switch + eigen Respond), 5xx komt uit foutuitgangen ("we hebben het geprobeerd, het brak").
Eén Respond, expressiegedreven code. Verschillen foutpaden alleen in nummer en boodschap (dezelfde bodyvorm, dezelfde headers), splits dan niet naar N Respond-nodes via een Switch. De Respond-node accepteert expressies in zowel de responscode als de body — bereken de code inline met een korte IIFE die op basis van de foutboodschap 400/429/504/502/500 teruggeeft. Bewaar Switch + meerdere Responds voor paden die structureel verschillen (andere headers, andere bodyvorm, redirects).
De standaard envelope is { "error": "<code>", "message": "<leesbare tekst>" } — de HTTP-status
zegt al succes-of-fout, dus geen aparte ok: false-vlag nodig. Lek nooit interne details
(stacktraces, SQL, upstream-bodies, tokens) in de respons — log die privé, geef een
geschoonde boodschap terug.
Workflow-brede foutworkflow (het vangnet)
Per-node-uitgangen vangen de fouten die je hebt voorzien op de nodes die je bedraad hebt. Een foutworkflow vangt al het andere: een node die je vergat te bedraden, een crash tussen nodes, een workflowbrede timeout, een trigger die faalt. Voor onbewaakte workflows is dit het vangnet dat "het stopte stilletjes" verandert in "er kwam een alert binnen".
Bouw dit als een aparte workflow die start met een Error Trigger-node. n8n roept die aan met de faalcontext (executie-ID, link naar de editor, laatst uitgevoerde node, foutnaam/-boodschap/ timestamp, workflownaam). Minimale versie — vastleggen → notificeren:
Error Trigger → Set (alert opbouwen uit executie + fout) → Slack/mail (naar #incidenten)Een goede alert bevat de workflownaam, een link naar de editor én naar de gefaalde executie, de naam van de gefaalde node, en de echte foutboodschap (niet "Workflow failed").
Twee valkuilen om vooraf te benoemen:
- De recursieval. Notificeert de foutworkflow via Slack en is Slack juist wat down is, dan faalt de foutworkflow ook — en de oorspronkelijke fout verdwijnt. Notificeer op een ander kanaal dan je gemonitorde workflows gebruiken (bijvoorbeeld: workflows alarmeren via Slack, de foutworkflow gebruikt e-mail), en voeg een terugvaloptie toe (wegschrijven in een aparte tabel) zodat een mislukte notificatie toch een spoor achterlaat.
- Een "afgehandelde" fout borrelt niet omhoog. Is de foutuitgang van een node bedraad naar een no-op die de data laat vallen, dan beschouwt n8n de fout als afgehandeld en vuurt de foutworkflow niet. Vang per node alleen af als je ook echt iets met de fout doet.
Wat via de tooling niet lukt: het toewijzen van de foutworkflow (instantiestandaard of per workflow) is een instelling in de n8n-UI zelf (Workflow Settings → Error Workflow) — daar bestaat geen programmatische route voor. Bouw de foutworkflow, en vertel de gebruiker precies welke UI-stap nodig is, en dat die herhaald moet worden (of als instantiestandaard gezet) voor elke onbewaakte workflow.
Wat niet beschikbaar is via de programmatische route
Naast het toewijzen van de foutworkflow zijn ook andere workflow-instellingen (Save Execution
Data, tijdzone, timeout, aanroepbeleid) alleen via de UI in te stellen, en instantiebrede
foutlogging (Sentry, serverlogs) is instantieconfiguratie buiten de workflows om. Wat wél
programmatisch kan: de foutworkflow bouwen, onError/retryOnFail op nodes zetten,
foutuitgangen bedraden (sourceIndex: 1), valideren, veelvoorkomende problemen automatisch
oplossen, testen, en gefaalde executies inspecteren.
Antipatronen
| Antipatroon | Wat er misgaat | Fix |
|---|---|---|
onError gezet maar foutuitgang niet bedraad | Fout stilzwijgend weggegooid; run toont als geslaagd | Bedraad sourceIndex: 1 naar een echte handler, of zet onError terug op stopWorkflow zodat het luid is |
Foutuitgang bedraad maar onError niet gezet | Slot vuurt nooit; handler onbereikbaar; workflow stopt bij fout | Zet onError: "continueErrorOutput" |
| Webhook → verwerken → respond, geen foutpad | Aanroeper krijgt timeout of generieke 500 | Bedraad elke falende node se foutuitgang naar een Respond |
Foutpad geeft 200 met een {error}-body terug | Client van de aanroeper leest succes; hun foutafhandeling vuurt nooit | Zet responseCode expliciet op 4xx/5xx bij foutresponses |
Eén 500 internal_error voor alles | Aanroeper kan eigen fout niet onderscheiden van jouw storing | Koppel oorzaak aan status (4xx aanroeper, 5xx jij) |
| Fouten opvangen in een Code-node en als data teruggeven | Downstream verwerkt foutvormige data en gaat door | Laat het gooien; gebruik onError: "continueErrorOutput" + bedraad pad |
Netwerknode zonder retryOnFail | Elke tijdelijke 429/hapering wordt een 5xx; alerts vuren op ruis | retryOnFail: true, maxTries: 3, waitBetweenTries: 5000 |
| Switch → N Responds die alleen in statuscode verschillen | 5 nodes voor wat één Respond kan | Bereken de code inline in één expressiegedreven Respond |
| Onbewaakte workflow zonder foutworkflow | Een echte storing gaat nergens heen | Bouw een Error Trigger-workflow + wijs 'm toe in de UI |
| Foutworkflow notificeert hetzelfde kanaal als de gemonitorde workflows | Kanaal down → foutworkflow faalt ook → fout verdwijnt | Ander kanaal + terugvaloptie |
| Foutdetails (stack/SQL/tokens) lekken in de respons | Interne details blootgesteld aan aanroepers/aanvallers | Privé loggen, geschoonde boodschap teruggeven |
Checklist
Voor een API/webhook-workflow: webhook-trigger gebruikt responseNode-modus, input vooraf
gevalideerd met een 4xx-Respond, elke falende node heeft continueErrorOutput én bedraad
main[1], netwerknodes hebben retry geconfigureerd, elk foutpad eindigt bij een Respond met
expliciete 4xx/5xx-code, statuscode past bij de oorzaak, foutbody bevat geen stacktraces/SQL/
tokens, en beide helften zijn geverifieerd door de workflow op te halen.
Voor een onbewaakte (gepland/cron/queue) workflow: netwerknodes hebben retry, er bestaat een Error Trigger-workflow (vastleggen → notificeren, optioneel retry), de foutworkflow notificeert op een ander kanaal met een terugvaloptie, en de foutworkflow-instelling is toegewezen in de n8n-UI.
Samenvatting
Onthoud: de default is stilte. Foutafhandeling is twee stappen — maak dat de fout routeert
(per-node onError + bedrade uitgang, of een vangnet-foutworkflow) en maak dat hij spreekt (een
statuscode en body die de waarheid vertellen). Een halve stap is erger dan geen stap, want het
oogt afgerond.
Praktijkvoorbeeld (NL)
Een Nederlandse boekhoudservice voor zzp'ers laat een n8n-workflow elke nacht facturen ophalen
uit een externe factuur-API en ze doorzetten naar het eigen klantportaal. Aanvankelijk stopte de
hele nachtelijke run stil bij elke tijdelijke 502 van de factuur-API, zonder dat iemand het merkte
tot een klant een week later belde over een ontbrekende factuur. Na het toepassen van deze skill
kreeg de HTTP Request-node retryOnFail met drie pogingen, kreeg de node ook een bedrade
foutuitgang naar een Error Trigger-workflow die via e-mail (bewust een ander kanaal dan de Slack
die voor gewone meldingen wordt gebruikt) het administratieteam waarschuwt met de naam van de
gefaalde klant en de echte foutmelding, in plaats van een generieke "workflow mislukt".