Skip to Content

Odoo Integrations

Odoo Integration Architecture: Patterns and Trade-Offs

Most integration failures are not coding failures. They are two systems that were never told which one owns the data, connected by something that has no memory of what it already sent. Both problems are decided by architecture, before anyone writes a line.

Part of our guide to Odoo Integrations

Decide ownership before anything else

Every field that exists in both systems needs one owner. Not a preference, a rule: which system is allowed to change it, and which one only reads it.

Get this wrong and the symptoms are miserable to diagnose. A price corrects itself overnight. A customer address reverts after an edit. Two systems overwrite each other on a loop, each one behaving exactly as designed.

Write the ownership map before choosing any technology. One row per shared entity, one column per field, one owner per row. The document is boring and it prevents the single most expensive class of integration bug.

Where both sides genuinely need to write, split the field rather than sharing it. The webshop owns the web description, Odoo owns the internal one. Two fields with one owner each beats one field with two.

Four patterns

Direct API calls. Odoo talks to the other system, or the other system talks to Odoo, with nothing in between. Fewest moving parts, cheapest to build, and every failure is yours to handle. Right for one or two integrations with simple mappings.

Middleware or an iPaaS. A platform sits between them, handling transformation, retries, queueing and monitoring. Costs money and adds a system to understand, and pays for itself once you have several connections, because otherwise you build retry and monitoring yourself, separately, for each one.

File exchange. CSV or XML dropped somewhere on a schedule. Unfashionable and still correct for high-volume batch work, particularly with older systems and with partners who will not build an API for you.

Event-driven webhooks. Systems notify each other when something happens rather than asking repeatedly. Near real time and efficient, and it requires you to handle delivery failure, out-of-order arrival and duplicate delivery, because every webhook sender will eventually do all three.

Most real architectures mix these. Webhooks for urgent events, a nightly batch to reconcile what the webhooks missed. The batch is not redundant, it is what makes the webhooks survivable.

Real time is rarely the requirement

Real time sounds obviously better and is usually the expensive answer to a question nobody asked.

Ask what actually depends on the delay. Stock levels on a busy webshop genuinely need to be current, because the cost of overselling is real. A customer address does not need to propagate in under a second, and neither does yesterday's invoice.

Batch is cheaper, easier to debug because you can inspect a whole run, easier to replay, and far kinder to rate limits. Reach for real time where the business consequence justifies it and use a schedule everywhere else.

Match on identifiers, not on names

Store the external system's identifier on the Odoo record. A field holding the Shopify customer id, the Stripe customer id, the marketplace order reference.

Then matching is exact. Without it, integrations match on name or email, and both are unstable: people change email addresses, names have three spellings, and two genuine customers can share one. Every duplicate-record problem in an integration traces back to matching on something that was never an identifier.

Storing the external id also makes the sync idempotent: replaying a message updates the record it already created rather than creating a second one. That property is what lets you re-run a failed batch without fear, and it is worth designing for from the first day.

Plan for failure, because it is normal

The other system will be down. Its certificate will expire. It will rate limit you mid-batch. A message will arrive twice, or out of order.

Three things make this survivable.

Retry with backoff, not immediately and not forever. Immediate retries during an outage are indistinguishable from an attack.

A dead letter queue, or its equivalent: somewhere failed messages land so a human can see them. Failures that only exist in a log file are failures nobody knows about.

Monitoring that alerts on silence. The failure nobody catches is not the error, it is the sync that stopped running altogether. Alert on "no successful run in N hours", because that is the condition an error-only alert cannot see.

Where the code lives in Odoo

Integration logic belongs in the model layer, like any other business logic. Controllers receive and validate; models decide.

For outbound calls, do not make the HTTP request inside the user's save. A slow or unreachable endpoint then blocks the interface, and a failure rolls back their work. Queue it and let a scheduled action send it.

For inbound, a controller receiving a webhook should do the least possible: verify the signature, store the payload, return quickly. Process it afterwards. Senders time out and retry, and slow processing inside the request handler turns one message into several.

The mapping document nobody writes

Before building, write down for every synchronised entity: the fields, the owner of each, the direction, the trigger, the matching identifier, and what happens on conflict.

It takes an afternoon. It is the artefact that makes the integration debuggable two years later, when the person who built it has moved on and the only remaining question is which side was supposed to win.

The specific ways this goes wrong in practice are collected in integration mistakes.

FAQ

Questions, answered.

Should we integrate Odoo directly or use middleware?

Direct is right for one or two integrations with simple mappings. Middleware earns its cost once you have several systems, need retries and monitoring you did not build, or need transformation logic that does not belong in either system. The deciding factor is usually the number of connections, not their difficulty.

What is the most important decision in an Odoo integration?

Which system owns each piece of data. Without a single owner per field, both sides eventually write the same record and the last writer wins at random. Almost every long-running sync problem traces back to this being left implicit.

How do we stop an integration creating duplicate records?

Store the external system's identifier on the Odoo record and match on it, rather than matching on name or email. Then a repeated message updates the existing record instead of creating a second one, which also makes the sync safe to replay.

With CODEerts

Want your systems talking properly?

We are certified Odoo partners. We connect Odoo to stores, payment providers, messaging and reporting tools, and we maintain those connections through upgrades.

See how we can helpBook a call