Bulletproof Workflows
Module 04 · foundations · intermediate
Lesson 04 · Bulletproof Workflows
Goal
Make workflows you can trust in production: split reusable logic into sub-workflows, handle expected errors gracefully, and get alerted about unexpected ones. Clients don't pay for workflows that work in demos; they pay for ones that don't silently break.
Prerequisites
- Modules 01–03
Concepts
- Sub-workflow: a workflow that starts with When Executed by Another Workflow and is called with an Execute Workflow node. Like a function: defined inputs, returns its last node's output.
- Stop and Error: deliberately fails the execution with your message. Use it for business-rule violations ("cannot divide by zero").
- Error output: any node's Settings → On Error → Continue (using error output) adds a second output. Failures go there instead of crashing the workflow. This is for expected errors.
- Error workflow: a separate workflow starting with an Error Trigger, selected in another workflow's Settings → Error Workflow. It runs when that workflow fails in production. This is for unexpected errors.
- Retry On Fail: node Settings → retry N times with a wait. For flaky APIs (Exercise 1).
- Published matters (n8n 2.x): sub-workflows and error workflows only run when they're published. Unpublished, a call fails with "Workflow is not active and cannot be executed" and the error workflow silently never fires.
Build it
Step 1: The sub-workflow
New workflow 04 - Divide (sub-workflow):
- When Executed by Another Workflow: input data mode Define using fields below; fields
a(Number),b(Number). - IF
Dividing by Zero?:{{ $json.b }}Number is equal to0. - true → Stop and Error
Fail: Division by Zero, messageCannot divide by zero. - false → Edit Fields
Calculate:a,b, andresult={{ Math.round(($json.a / $json.b) * 1e6) / 1e6 }}. - Save and Publish.
Step 2: The API
New workflow 04 - Safe Divide API:
- Webhook
Divide API: POST, pathdivide, respond using a Respond to Webhook node. - IF
Numbers?:{{ $json.body.a !== '' && $json.body.b !== '' && Number.isFinite(Number($json.body.a)) && Number.isFinite(Number($json.body.b)) }}Boolean is true. False → Respond to WebhookRespond Bad Input(400,{{ { error: 'a and b must be numbers' } }}). - True → Execute Workflow
Divide (sub-workflow): source Database, pick the sub-workflow, mapa={{ Number($json.body.a) }},b={{ Number($json.body.b) }}. - On that node: Settings → On Error → Continue (using error output). It now has Success and Error outputs.
- Success → Respond to Webhook
Respond OK:{{ { result: $json.result } }}. - Error → Respond to Webhook
Respond Calculation Error: response code 422, body{{ { error: $json.error } }}. The sub-workflow's Stop and Error message arrives in$json.error.
Step 3: The error workflow
New workflow 04 - Error Logger:
- Error Trigger.
- Edit Fields
Format Alert:workflow={{ $json.workflow.name }},failed_node={{ $json.execution.lastNodeExecuted }},message={{ $json.execution.error.message }},execution_url={{ $json.execution.url }}, and analertstring combining them. - No Operation
Send Alert (Exercise 2)as a placeholder. - Save and Publish.
Step 4: Wire it up and add a crash button
- In 04 - Safe Divide API: workflow Settings → Error Workflow → 04 - Error Logger.
- Add a second Webhook
Crash API: POST, pathcrash, Respond When Last Node Finishes → Stop and ErrorSimulate Crash. - Publish.
Test it
./test/test.sh
Expected: 9 PASS lines (200 results, 422 for division by zero, 400 for bad input, 500 for crash). Then open 04 - Error Logger → Executions: the latest run's alert reads:
🚨 04 - Safe Divide API failed at "Simulate Crash": Simulated crash to test the Error Workflow
http://localhost:5678/workflow/<id>/executions/<n>
Common problems
- 422 "Workflow is not active and cannot be executed": the sub-workflow isn't published.
- Error workflow never runs: it isn't published, it isn't selected in the main workflow's Settings, or you ran the workflow manually from the editor. Error workflows only fire for production executions.
- Crash returns 200: the crash webhook responds Immediately. Set it to When Last Node Finishes to see the 500.
- Error output never used: On Error is still Stop Workflow. Set it on the node that can fail.
- "Workflow does not exist" after import: IDs changed. Re-select the sub-workflow and error workflow.