Odoo + n8n: retry order creation without duplicates

Back
Kevin Riedl

11 min read · 5 Nov 2026
Last reviewed

Next
Made on your device, with no Instagram connection. We copy the post link for Instagram’s Link sticker.

Suppose Odoo saved the order. The response never reached n8n. An operator sees a failed execution and presses retry. Your recovery design now decides whether the customer receives one order or two.

Make Odoo order creation repeatable by assigning one durable identity to the intended operation, enforcing that identity where the record is written, and reconciling the target after an uncertain response. Keep the same identity and payload on retries. Stop changed payloads or unresolved outcomes for review instead of blindly creating another record.

Idempotency means repeating an operation leaves the same intended business result. That property belongs to the operation you have implemented; a retry setting cannot supply it on its own.

Research checked: 5 November 2026. Product facts use official Odoo 20 and n8n sources. We executed ten synthetic Python/SQLite cases, including a deliberately discarded response after a local commit. We did not run Odoo, n8n or PostgreSQL, interrupt a live network, or measure a customer integration.

Why can a failed n8n execution leave an Odoo record behind?

A failed n8n execution can leave an Odoo record behind because the target may finish its write before the caller receives the result. Treat a lost response as an unknown outcome until evidence resolves it. A failure indicator describes the observed execution; it does not prove that every earlier business effect was rolled back.

In our example, the shop requests an order for company A with source reference SO-77 and an amount of EUR 125.00. The target creates its record, then the response is lost. Repeating an ordinary create request can produce another record for the same purchase. The second request may return successfully, leaving the operator with a green execution and a duplicate.

n8n’s Retry On Fail documentation describes automatic retries and the wait between attempts. Those controls govern when another attempt happens. Your receiving operation must decide what a repeated attempt means.

The opposite failure matters too. Suppose the workflow writes “processed” before creating the order, then stops between those steps. A replay sees that marker and skips the work. There is no duplicate, but there is no order either. Track receipt separately from completion, and inspect the final target record before closing an incident.

What should identify one intended Odoo order creation?

Identify an intended Odoo order creation with a durable business key scoped to its source and owning company. Preserve the approved payload alongside it. Delivery IDs and execution IDs identify attempts; they do not necessarily identify the purchase those attempts should create. Decide how a legitimate revision differs from a replay before enabling automatic retries.

Use a contract such as the following. These names are our proposed integration fields, not built-in Odoo fields or a promise that Odoo recognizes a particular HTTP header.

Proposed contract for one order-creation operation
FieldExamplePurpose
Source and companyshop, AKeep independent business owners in separate namespaces.
Source business referenceSO-77Identify the purchase across deliveries and manual replays.
Command and revisioncreate_draft_order, 1Distinguish this creation from a later approved amendment or another business action.
Payload fingerprintDigest of the agreed business fieldsReject reuse of the key with changed content.
Attempt referencesWebhook delivery and n8n execution IDsTrace individual attempts without making each attempt a new purchase.
Target evidenceOdoo record ID, company and accepted stateShow which business record fulfilled the operation.

n8n’s idempotency guide notes that manual retries or retriggers receive a new execution.id and recommends input-derived keys across runs. Keep the business identity in durable storage before relying on it for recovery.

Specify which fields the fingerprint covers. An amount or currency change should not disappear behind a matching order number. Exclude delivery metadata that legitimately changes on every attempt. Normalize only according to the agreed contract: stripping leading zeroes from a customer identifier may change its meaning.

In the fixture, replaying EUR 125.00 returns the original record. Reusing the same key with EUR 130.00 raises a payload conflict and leaves the original amount intact. A reviewer must decide whether the later input is a correction, a new approved revision or an upstream defect.

Where must duplicate prevention be enforced?

Enforce duplicate prevention at the boundary that commits the business record. A receiving operation needs to claim the scoped key and commit its result consistently with the write. A separate n8n receipt table can coordinate attempts, but it cannot by itself make a later Odoo write part of the same database transaction.

The official JSON-2 transaction guidance says calls run in separate transactions and related operations requiring one transaction should execute through one server method. The existing Odoo API architecture guide covers the hosting and protocol constraints that determine which implementation is available.

A custom receiving method could validate the current caller, inspect an operation receipt, create the permitted record when needed, and save its result in one transaction. It must also resolve simultaneous requests safely. Merely searching for SO-77 before creating it leaves a gap: two workers can both search before either writes.

The n8n Odoo node reference lists Custom Resource CRUD operations. That list does not establish that a create operation implements your receipt contract. Verify the installed node and receiving method. Sending an Idempotency-Key header only helps when the receiver implements it.

Where you cannot implement or verify a destination contract, document the weaker guarantee. Controlled serialization and destination lookups can reduce uncertainty, but every writer and replay path must participate. Leave an unresolved write for investigation when the evidence cannot distinguish “not created” from “created but not yet found.”

External effects need their own boundaries. A receipt committed with an order does not prove that a later email was sent once. The email duplicate-send recovery guide examines that separate operation; do not silently include sending or payment in a promise about order creation.

What did the ten local failure cases show?

The ten local cases showed that duplicate prevention and completion recovery require different evidence. The fixture reproduced both duplicate records and lost work, then recovered the original record through a target-side atomic receipt. Every expected observation matched. These results describe our Python/SQLite model, including its locking behavior, and do not establish the behavior of Odoo or n8n.

The target model contains an order table and an operation-receipt table in one SQLite database. Its successful path writes them in the same transaction. A failure hook deliberately discards the response after commit; another stops before commit. The concurrent case starts two local workers, while the unsafe search case uses a documented interleaving of reads and writes.

Executed synthetic cases on 5 November 2026
CaseExpected observationObserved
Blind retry after the first create committedTwo records for one intended creation2 records
Processed marker saved before the effect, then interruptionReplay skips missing work0 records
Two searches complete before either unprotected createBoth attempts create2 records
Atomic target receipt, response lost after commitReplay returns the original draft record1 record; same ID
Same operation key, changed amountConflict; original record remains unchangedConflict; EUR 125.00 retained
Same source reference in separately authorized companies A and BOne independent record per company2 records in separate namespaces
Replay after authority for company A is removedCurrent authorization blocks accessDenied
Failure before the atomic transaction commitsNo committed record; retry creates one0 before retry; 1 afterward
Two simultaneous requests to the model’s protected targetBoth resolve to the same record1 record; same ID
Client receipt exists, target lookup is inconclusiveRetain an unknown outcomeunknown

Download the runnable retry model and recorded model results. Save the Python file locally and run:

python3 retry-model.py

The script uses temporary local databases and no network. It is not an Odoo module. The model’s draft state is its own field; the run did not create a quotation, reserve stock, post accounting or send a message in Odoo. Use the example to challenge the design, then run acceptance checks against the real receiving operation.

How should the operator recover an unknown result?

Recover an unknown result by matching the intended operation to evidence at the destination before choosing another write. Recheck the caller’s current authority and the payload revision. Preserve the original key throughout the investigation. If the lookup cannot establish what happened, retain UNKNOWN and assign an owner instead of turning absence of evidence into a successful replay.

Proposed recovery decisions for the order-creation command
EvidenceDecisionNext action
One matching target result with the accepted company and contentCreation completedStore the target reference and continue only with separately authorized downstream work.
Same key with changed business contentConflictReview the proposed revision; do not overwrite the original result during replay.
More than one target matchDuplicate incidentStop new writes for the operation and investigate dependent records before correcting anything.
Current caller no longer has authorityBlockedEscalate through the approved owner without reusing elevated credentials.
No conclusive destination resultUnknownUse a verified repeatable receiving operation, or investigate manually before another create.

“Creation completed” is an integration outcome, not an instruction to confirm a quotation or post an invoice. Keep the receipt’s lifecycle separate from the business record’s state.

An n8n error workflow is configured for a workflow and starts with Error Trigger. Use it to route the operation key and destination evidence to the recovery owner. A retry queue needs bounded attempts and a visible unresolved state; alerting alone does not repair the write.

Record the lookup scope. Searching under the wrong company or a narrower user role can return no visible match even when a record exists. An operator needs the source reference, exact target identity and evidence collected under an authorized context. The authenticated-action verification guide covers the equivalent evidence problem for browser actions.

Which acceptance evidence is required in Odoo and n8n?

Accept the Odoo and n8n integration only after testing repeated commands on its actual configuration and inspecting the resulting business records. Include response loss after a committed write and simultaneous attempts, with both permitted and denied users. The required outcome is the intended record in the intended state, with no unauthorized or unexplained additional effects.

Use the proposed production acceptance cases. Every case starts as NOT_RUN. Record the Odoo version and module revision, n8n version and node settings, test identities, approved input, target query and observed result. A workflow screenshot is useful supporting evidence, but it cannot replace the record comparison.

Test the recovery paths your team will use: automatic retry, operator replay after a workflow edit, and replay after a restarted worker. Include a changed amount under the old key and a legitimate second company using the same source reference. Verify that a revoked identity cannot recover protected details merely because an earlier attempt succeeded.

For invoicing, name the intended state explicitly. Odoo’s invoice documentation distinguishes Draft creation from confirmation to Posted, which creates the journal entry. A returned invoice ID does not prove posting or accounting approval. Test any subsequent confirmation as its own authorized business command.

Run the rehearsal in an isolated environment with controlled external services. A duplicate-payment or email case needs a safe substitute for the real recipient. Finance and operations should approve their respective business outcomes; a developer’s assertion that the node succeeded cannot settle them.

When should automatic retries remain disabled?

Keep automatic retries for uncertain writes disabled until the receiving operation has a verified repeatability contract and an owner can reconcile its result. Reads or explicitly repeatable commands can follow their own bounded policy. The decision should change when your actual integration passes the relevant failure cases, with evidence of both completed work and denied operations.

We would start with one order-creation command and its acceptance record. Add downstream confirmation or sending only after its separate recovery boundary is specified. Agree the expected records through the discovery and requirements process, including who resolves an unknown outcome after business hours.

Wavect’s API integration service is the commercial route for implementing that record-transfer boundary. Bring one failing execution and its target evidence to the discussion. The useful first deliverable is a reproducible recovery case with a clear release decision.

Odoo and n8n retry FAQ

Does setting the batch size to one prevent duplicates?
A batch size of one limits the work grouped in that batch. It does not establish what happens when the same item is delivered again or another worker handles it concurrently. Test repeated delivery separately from batching. The receiving operation still needs a business identity and a verified way to return or reconcile its earlier result.
How should a manual replay with a new webhook delivery ID be handled?
Keep the delivery ID as an attempt reference and recover the intended business operation from its durable source identity. Decide whether the input represents the same approved revision. A new delivery ID alone should not create a new order. If the upstream system cannot supply a stable reference, define and persist one before enabling automated creation.
Can the same source order number exist in two companies?
Your integration contract must answer that question. The fixture permits the same source reference in companies A and B because they are separately authorized owners and company forms part of the key. That is an illustrative policy. Verify your actual source identifiers and Odoo ownership rules; do not use similar company names as an authorization rule.
Does creating an invoice mean that accounting has been posted?
No. Odoo distinguishes an invoice in Draft from the later confirmation that makes it Posted and creates its journal entry. Name the state your command is allowed to produce, then inspect that state during acceptance. A record ID or successful node output does not establish posting, payment reconciliation or approval of the accounting result.
How long should operation receipts be retained?
Choose a retention period from the actual replay horizon and the business process for corrections. Include delayed webhook delivery, operator recovery and restored workflow state in that decision. A short deduplication window can expire while an old business command remains replayable. Document what happens after expiry instead of assuming that an expired receipt makes a repeated purchase legitimate.
Can an error workflow repair every failed write automatically?
An error workflow can collect the evidence and route the incident. The recovery decision still depends on the destination result and the caller’s current authority. Automate only cases with a verified safe action. Changed payloads, multiple target matches and inconclusive lookups need an explicit review path; they should not become another blind create request.
Is the downloadable model a ready-to-install Odoo module?
No. It is a local Python and SQLite model that reproduces named failure schedules without contacting Odoo or n8n. Its transaction and authorization rules are deliberately small. Use the results to review an integration design, then execute the separate NOT_RUN acceptance cases on your actual Odoo version, modules and workflow configuration.

Build the product, not just the backlog

If this article maps to a real product decision, Wavect can help you scope, build, harden, or lead the software work with senior founder-level judgment.

Useful service paths:

Inbox, without the noise

Follow the work that matters to you

Get a short email when we publish something new. Follow the whole blog or only the problems you care about.

What would you like to receive?
Choose your topics

Free, double opt-in, no tracking pixels.

Back
Kevin Riedl

11 min read · 5 Nov 2026
Last reviewed

Next

Get the next Delivery and QA field note

One concise email when we publish. No tracking pixels, and no inbox filler.

Free, double opt-in, no tracking pixels.