When your workflow detects an invalid state that should not continue -- bad input data, missing prerequisites, or a violated business rule -- use the **Stop ...
When your workflow detects an invalid state that should not continue -- bad input data, missing prerequisites, or a violated business rule -- use the Stop and Error node to halt execution with a clear, descriptive message. This is different from an unexpected error; it is a deliberate, controlled stop that communicates exactly what went wrong.
Real-world example: An invoice processing workflow receives data from a webhook. Before processing, it checks that the invoice total matches the sum of line items. If they do not match, the workflow should stop immediately with a clear message rather than processing bad data.
Workflow structure:
[Webhook] --> [Code: Validate Invoice] --> [IF: Valid?]
|
+-------+-------+
| |
[True] [False]
| |
[Process Invoice] [Stop and Error]
```text
Validation Code node:
```javascript
// Code node: "Validate Invoice"
const invoice = $input.first().json.body;
const errors = [];
// Check required fields
if (!invoice.invoice_id) errors.push('Missing invoice_id');
if (!invoice.customer_id) errors.push('Missing customer_id');
if (!invoice.line_items || invoice.line_items.length === 0) {
errors.push('No line items');
}
// Check total matches line items
if (invoice.line_items && invoice.total !== undefined) {
const calculatedTotal = invoice.line_items.reduce(
(sum, item) => sum + (item.quantity * item.unit_price), 0
);
const roundedCalculated = Math.round(calculatedTotal * 100) / 100;
const roundedTotal = Math.round(invoice.total * 100) / 100;
if (roundedCalculated !== roundedTotal) {
errors.push(
`Total mismatch: stated ${roundedTotal}, calculated ${roundedCalculated}`
);
}
}
return [{
json: {
valid: errors.length === 0,
errors,
invoice
}
}];
```text
Stop and Error node configuration:
```yaml
# Stop and Error node settings
Error Type: Workflow
Error Message: >
Invoice validation failed for invoice {{ $json.invoice.invoice_id }}:
{{ $json.errors.join('; ') }}.
Workflow halted to prevent processing invalid data.
Source customer: {{ $json.invoice.customer_id }}.
```text
The error message appears in:
```yaml
- The execution log (with full details)
- The Error Workflow notification (if configured)
- The Respond to Webhook response (if using Respond to Webhook node on the error branch)
```text
> **Tip: Stop and Error vs. throwing in Code node**
>
> Both halt the workflow, but Stop and Error is a visible node in the canvas that clearly communicates intent to anyone reading the workflow. It also provides a structured error message. Throwing an error in a Code node works but looks like a bug rather than intentional behavior.
Use Stop and Error as guardrails throughout your workflow to catch invalid states early and communicate clearly why processing was halted.
**Related:** [Use "Pin Data" to Freeze Node Output](../testing-and-debugging/01-use-pin-data-to-freeze-node-output.md) | [Break Large Workflows into Sub-Workflows](../workflow-architecture/01-break-large-workflows-into-sub-workflows.md)
I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.