Tips > Reliability & Performance

Use the Stop and Error Node for Intentional Workflow Halts

When your workflow detects an invalid state that should not continue -- bad input data, missing prerequisites, or a violated business rule -- use the **Stop ...

TipAdvanced2 min read

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)

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.