Kingdom of KumarBack to home

Fix · n8n

Stop n8n retries from repeating a create, send or charge

By Mayank Raj · Kingdom of Kumar · Published 5 November 2026

The problem

“Multi-item node that fails on item 3: are items 1 and 2 repeated on retry?”

Farid Ullah, Retry from the failed node: does it repeat the failed node's external effect, and how do you guard against it? (n8n community forum, 4 Nov 2026)

Same problem elsewhere: n8n forum: atomic claim for webhook duplicates · r/n8n: does Retry execution repeat the external action?

Short answer: Yes. Retry runs the failed node again with all of its input items, and a timeout after the remote side accepted the call looks the same as a failure. The guard is a ledger keyed on the business item (action plus record id, never the execution id). Claim the key with INSERT ... ON CONFLICT DO NOTHING before the write, mark it done after, skip done keys on retry, and send keys stuck in started to a person.

Most answers say "use an idempotency key", which is right, but many chat and email APIs don't accept one. A ledger in your own database works for any API. The key detail is the third state: if a row says started and the response was lost, nobody knows whether the send went through, so the safe move is a human check, not another send.

Download

Everything is free to use. The scripts are Python 3 standard library only and default to read-only or dry-run. Each one ships with an offline test on sample data.

Step by step

1. Create the ledger table

Run the CREATE TABLE from schema.sql in any Postgres your n8n can reach.

CREATE TABLE IF NOT EXISTS side_effect_ledger (
  idem_key     text PRIMARY KEY,   -- '<action>:<business id>'
  action       text NOT NULL,
  status       text NOT NULL CHECK (status IN ('started','done','failed')),
  execution_id text,
  started_at   timestamptz NOT NULL DEFAULT now(),
  finished_at  timestamptz,
  response     jsonb
);

2. Key on the business item

Build idem_key as action name plus a stable record id, for example create_invoice:INV-1042. Do not use the execution id: a retry creates a new execution, so the lookup would pass and the write would run again.

3. Split to one item per record

Retry re-runs a node for all its input items. If each record goes through the guard on its own, items 1 and 2 are already marked done when item 3 fails.

4. Claim before the write

This statement is atomic. It returns claimed = true for exactly one caller.

WITH c AS (
  INSERT INTO side_effect_ledger (idem_key, action, status, execution_id)
  VALUES ($1, $2, 'started', $3)
  ON CONFLICT (idem_key) DO NOTHING
  RETURNING idem_key
)
SELECT $1::text AS idem_key, EXISTS (SELECT 1 FROM c) AS claimed;

5. Write, then mark done

If claimed, call the external API (the template also sends an Idempotency-Key header with the same key for APIs that support it, such as Stripe), then UPDATE the row to done with the response.

6. Handle the not claimed path

Look the key up. done: skip. failed (a definite rejection): reclaim and send again. started: the outcome is unknown, so alert a person to check the remote system.

7. Import and test

Import guard.n8n.json, create a Postgres credential named "Ledger Postgres", and run it twice against a throwaway endpoint. The second run should skip every item. Then run the offline simulation.

python3 simulate.py --mock
python3 test_fix.py

What this does not cover

Want this done for you?

Kingdom of Kumar runs it for $500/month, first automation live in 48 hours. Claude + Grok Bot + $500/month (my fee). Email me the task you hate doing every week.

Email me the task

Related: Custom automation vs n8n and Zapier · All fixes