Guarantee exactly-once side effects

Hi everyone,
I’m running production workflows in n8n where reliability matters more than throughput, and I’m struggling with exactly-once execution guarantees.
Problem Scenario
The workflow:
Consumes data from an API (or webhook)
Transforms it
Triggers side effects (DB writes, webhooks, emails, 3rd-party updates)
Constraints:
Queue mode with multiple workers
Automatic retries on failure
External APIs occasionally timeout or return 5xx
n8n restarts or worker crashes can happen mid-execution
Observed Issues
A node times out, workflow retries
Side effects from the previous attempt already happened
Downstream systems receive duplicate updates
Continue On Fail avoids crashes but not duplicate effects
There’s no built-in exactly-once semantic

Describe the problem/error/question

How do you design workflows that guarantee exactly-once side effects?
Where do you place idempotency keys in workflow?
Do you rely on DB constraints, external state, or orchestration patterns?
Are sub-workflows or external queues the only safe solution?

What is the error message (if any)?

Please share your workflow

(Select the nodes on your canvas and use the keyboard shortcuts CMD+C/CTRL+C and CMD+V/CTRL+V to copy and paste the workflow.)

Share the output returned by the last node

return items.map(item => ({
json: {
userId: item.json.id,
payloadHash: createHash(item.json)
}
}));INSERT INTO audit_log(user_id, payload_hash)
VALUES (:userId, :payloadHash);

Information on your n8n setup

  • n8n version:
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app):
  • Operating system:
1 Like

Hi @Roseline Exactly-once in n8n isn’t about stopping retries, it’s about making retries safe using idempotency and data constraints.

Try to

  1. Create an idempotency key
    For every action (DB write, webhook, email), generate a key like:
    user_id + action_type or a hash of the payload

  2. Store that key before/with the action

    INSERT INTO side_effect_log(key)
    VALUES (:key)
    ON CONFLICT DO NOTHING;

  3. Make database writes replay-safe
    Always use UPSERT / ON CONFLICT so running the same step twice doesn’t create duplicates.

  4. Retry only failed items
    Don’t retry the whole workflow. Retrying everything will replay side effects.

  5. Isolate side effects when possible
    Use a parent workflow to coordinate and a child workflow to process one item at a time.

2 Likes

Niffzy has it right, idempotency keys are really the only way to handle this reliably. n8n doesn’t have built-in exactly-once semantics so you have to push that logic to your downstream systems. I’d generate a hash of the payload plus timestamp at the start of the workflow and pass it through to every node that does a write, then use ON CONFLICT DO NOTHING or check-before-insert patterns on the receiving end. External queues like Redis with SETNX can help too if your DB doesn’t support upserts cleanly.

1 Like

Thanks @Niffzy @achamm for the reply

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.