An idempotent workflow produces the same result whether it runs once or multiple times with the same input.
An idempotent workflow produces the same result whether it runs once or multiple times with the same input. This is critical because n8n may re-execute workflows due to retries, duplicate webhook deliveries, or manual re-runs during debugging. Without idempotency, you get duplicate records, double charges, or repeated notifications.
Real-world example: A workflow processes incoming orders. Without idempotency, a duplicate webhook from Shopify creates two records in the database and sends two confirmation emails.
Idempotency patterns:
Pattern 1: Upsert instead of Insert
Use ON CONFLICT (unique_key) DO UPDATE instead of INSERT.
If the record exists, it gets updated (no duplicate).
Pattern 2: Check-then-act
Before creating a record, query whether it already exists.
Skip creation if found.
Pattern 3: Idempotency key
Store a hash of the input and check it before processing.
If the same input was already processed, skip entirely.
```text
Implementing Pattern 3 in a Code node at the start of every workflow:
```javascript
const crypto = require('crypto');
const inputData = $json;
// Generate a deterministic hash of the input
const idempotencyKey = crypto
.createHash('sha256')
.update(JSON.stringify({
order_id: inputData.order_id,
// Include only fields that define uniqueness
updated_at: inputData.updated_at
}))
.digest('hex');
return [{
json: {
...inputData,
_idempotency_key: idempotencyKey
}
}];
```text
Follow with a database check:
```sql
-- Postgres node: Check if already processed
SELECT idempotency_key
FROM processed_events
WHERE idempotency_key = '{{ $json._idempotency_key }}'
LIMIT 1;
```text
Then an IF node:
```text
{{ $json.idempotency_key ? true : false }}
→ true (already exists): Skip processing, return 200 OK
→ false (new): Continue to processing pipeline
```text
At the end of the workflow, record the key:
```sql
-- Postgres node: Mark as processed
INSERT INTO processed_events (idempotency_key, workflow_name, processed_at)
VALUES ('{{ $json._idempotency_key }}', '{{ $workflow.name }}', NOW())
ON CONFLICT (idempotency_key) DO NOTHING;
```text
> **Tip: Database Upserts Are Your Friend**
>
> Most n8n database nodes support upsert operations. In the Postgres node, use **Operation: Upsert** and define the conflict column. This is the simplest path to idempotent database writes without separate check-then-act logic.
Idempotency is not optional for production workflows. It is the single most important reliability pattern.
**Related:** [Always Set an Error Workflow on Every Production Workflow](../error-handling-and-reliability/01-always-set-an-error-workflow-on-every-production-workflow.md) | [Use "Pin Data" to Freeze Node Output](../testing-and-debugging/01-use-pin-data-to-freeze-node-output.md)
I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.