Skip to content

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):

  1. When Executed by Another Workflow: input data mode Define using fields below; fields a (Number), b (Number).
  2. IF Dividing by Zero?: {{ $json.b }} Number is equal to 0.
  3. true → Stop and Error Fail: Division by Zero, message Cannot divide by zero.
  4. false → Edit Fields Calculate: a, b, and result = {{ Math.round(($json.a / $json.b) * 1e6) / 1e6 }}.
  5. Save and Publish.

Step 2: The API

New workflow 04 - Safe Divide API:

  1. Webhook Divide API: POST, path divide, respond using a Respond to Webhook node.
  2. 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 Webhook Respond Bad Input (400, {{ { error: 'a and b must be numbers' } }}).
  3. True → Execute Workflow Divide (sub-workflow): source Database, pick the sub-workflow, map a = {{ Number($json.body.a) }}, b = {{ Number($json.body.b) }}.
  4. On that node: Settings → On Error → Continue (using error output). It now has Success and Error outputs.
  5. Success → Respond to Webhook Respond OK: {{ { result: $json.result } }}.
  6. 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:

  1. Error Trigger.
  2. Edit Fields Format Alert: workflow = {{ $json.workflow.name }}, failed_node = {{ $json.execution.lastNodeExecuted }}, message = {{ $json.execution.error.message }}, execution_url = {{ $json.execution.url }}, and an alert string combining them.
  3. No Operation Send Alert (Exercise 2) as a placeholder.
  4. Save and Publish.

Step 4: Wire it up and add a crash button

  1. In 04 - Safe Divide API: workflow Settings → Error Workflow → 04 - Error Logger.
  2. Add a second Webhook Crash API: POST, path crash, Respond When Last Node Finishes → Stop and Error Simulate Crash.
  3. 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.