Use Odoo 20 automation rules for well-defined changes inside Odoo, use webhooks to signal events between systems, and use a custom integration service or module when the result must survive duplicates, downtime, out-of-order messages, or partial failure. A webhook is a delivery mechanism. It is not, by itself, a guarantee that an order will be created once, stock will be updated correctly, or an invoice will be reconciled.
This distinction matters when Odoo is connected to an online store, delivery provider, payment platform, mobile application, or another ERP. A demonstration might work with one request. Production must also work when the sender retries, two workers race, an event arrives late, or Odoo successfully writes a record but the network drops before the response reaches the caller.
The official Odoo 20 automation-rule documentation describes record-change triggers, time-based rules, inbound webhook triggers, and an outbound Send Webhook Notification action. These are useful building blocks. Choosing the right boundary around them is the engineering decision.
Four mechanisms that sound similar but solve different problems
| Mechanism | Good fit | Boundary to remember |
|---|---|---|
| Studio automation rule | Updating a field, creating an activity, or notifying someone after an Odoo record changes | It operates on configured trigger and domain conditions; careless update triggers may execute more than once. |
| Inbound Studio webhook | Receiving an external event that should trigger a defined Odoo action | The sender and receiver still need an agreement on identity, payload validation, and failure handling. |
| Outbound webhook notification | Sending selected Odoo record fields by HTTP POST to another system | Sending a notification is not the same as ensuring the destination applied the business change exactly once. |
| Scheduled action / reconciliation job | Checking due records, processing a controlled backlog, or verifying that two systems still agree | Execution is periodic rather than instantaneous, and the job must be safe to rerun. |
For multi-step business transactions, combine mechanisms rather than forcing one feature to do all the work. A webhook can start a process; a persistent queue can hold it; a worker can apply business logic; a scheduled reconciliation can detect anything missing.
The official Odoo 20 rules guide documents five trigger categories: Values Updated, Email Events, Timing Conditions, Custom, and External. A Studio webhook belongs to the External category. An outbound notification is an action taken after a trigger. These are opposite directions of communication, not interchangeable settings.
Where duplicate automations begin: the trigger
A frequent issue starts with a rule configured to run On create and edit. The record is saved, the rule updates a field, another process saves the same record, and the rule runs again. If the rule also creates a follow-up record or sends an external request, a repeated trigger can become a duplicate business action.
Odoo 20's documentation gives a direct warning: when On create and edit is configured without selecting fields in When updating field, the automation may execute multiple times per record. It also distinguishes the Before Update Domain from the Apply on domain, which describe conditions before and after the change. The before-update condition is not checked when the record is initially created. See the trigger and conditions reference.
For a local workflow such as "notify purchasing when a purchase request first enters Approved":
- Select the precise state field that represents the transition.
- Define the expected state before and after the update, where the selected trigger supports it.
- Make the action harmless if it is invoked again for the same record and transition.
- Test another edit to the already-approved record: it must not send another approval notification accidentally.
Do not assume the On UI change trigger covers imports, API writes, scheduled jobs, or another automation. Odoo specifically documents this trigger as a manual form-view interaction, not a general server-side change listener.
A successful trigger is not a successful integration
Consider an e-commerce order whose paid status triggers an outbound message from Odoo. The destination might accept the HTTP connection and then fail while creating its own record. Or it might finish successfully but the response never arrive. Without a durable processing record, the sender cannot distinguish a failed operation from a completed operation with a lost acknowledgment.
This is a distributed-systems problem. A Studio rule can express when to try. Your integration design must specify what counts as completed and how to retry without changing the result.
A practical order-import example
Suppose a retailer receives a paid order from Shopify and wants exactly one corresponding sales order in Odoo.
A robust target flow is:
Shopify event
-> verify the sender and parse the payload
-> record the event/delivery identity durably
-> enqueue a processing job
-> find or create the corresponding Odoo order
-> record the Odoo order reference and job result
-> reconcile periodically against ShopifyThis is an architecture example, not a claim that Studio implements the entire flow out of the box. The queue, durable identity checks, and reconciliation may require middleware or a custom module, depending on the requirements and hosting model.
Shopify's own webhook overview warns that event ordering is not guaranteed and recommends reconciliation jobs because delivery can be missed or mishandled. Shopify also documents a delivery identifier and a separate event identifier for correlation. Those details are useful for Shopify integrations; other platforms must be checked against their own protocols.
Deduplicate two different things
Webhook delivery identity answers: Have we seen this notification before?
Business-object identity answers: Has the corresponding order already been created?
For Shopify, the official delivery verification guide documents the X-Shopify-Webhook-Id header for detecting duplicate deliveries and explains that separate subscriptions may produce separate delivery IDs for the same originating event.
A second guard should associate the source system, store, and external order ID with the Odoo sales order. That way, even if two different deliveries refer to one checkout, they do not create two sales orders. The chosen key should be enforced durably; an application-level "search, then create" check alone can still race under concurrent workers.
Use a database uniqueness constraint or another transactional idempotency mechanism for that mapping where the deployment architecture supports it. The exact Odoo model and migration should be designed and tested before installation; do not copy a generic SQL constraint into production without checking existing data.
Handle events that arrive out of order
An external platform may send "order updated" before "order created" reaches the integration. A delayed notification can also describe an older state than the one already applied in Odoo.
Avoid applying incoming updates blindly in arrival order. For each integrated object, define:
- the authoritative system for each field or status;
- an external revision, event time, or other ordering signal when the provider supports one;
- whether an old message should be ignored, replayed, or reloaded from the source API;
- whether a cancellation, refund, or return is a separate business operation rather than a status overwrite.
For Shopify specifically, its webhook documentation recommends using timestamps to handle unordered delivery. For other providers, verify their actual timestamp, revision, retry, and consistency semantics rather than assuming every webhook behaves like Shopify's.
When the external event lacks enough reliable state, treat it as a signal to fetch the current object from the source API. This costs an API read but can avoid overwriting new information with an old notification.
Security: an endpoint URL is not a complete policy
A webhook is an external entry point into a business system. Odoo 20 warns that incorrectly configured webhook rules can disrupt a database and recommends technical review. That matters when the action can create or modify invoices, contacts, stock records, or orders.
For a provider that signs deliveries, such as Shopify, validate the signature against the raw request body before trusting payload fields. Shopify's official verification guide explains the X-Shopify-Hmac-SHA256 header and the raw-body requirement. Never treat a copied JSON example as authenticated input.
A secure design also needs to answer:
- Which source systems may call the endpoint?
- Can this event create records, or only update records already linked to a known external ID?
- Which company and warehouse are permitted?
- Which fields can the sender change?
- How are credentials, secrets, and keys rotated?
- What is logged, and how are personal or payment details kept out of unnecessary logs?
Odoo-generated webhook URLs should be treated as confidential. If using an upstream verification gateway, keep the secret and its rotation process separate from the integration's business mapping. Do not expose privileged operations through a public route simply because the first test request succeeded.
Where Studio is enough—and where a custom module is justified
Studio is a sensible starting point for low-risk, reversible actions. Examples include adding an activity when a lead is assigned, flagging a stale request, or posting a controlled notification for a specific field transition. With careful domains and update fields, these can be understandable and maintainable.
A custom integration layer is justified when one failed or repeated request can create financial, stock, or customer-service damage. Indicators include:
- durable retries or a dead-letter queue;
- strict idempotency across concurrent workers;
- signed-request verification that must be enforced before any record mutation;
- transaction boundaries spanning several related Odoo records;
- complex multi-company or multi-warehouse mapping;
- replay tooling, metrics, and audit logs;
- high event volume or backpressure requirements.
A custom implementation is not automatically better. It adds code, deployment, security reviews, test coverage, and upgrade obligations. The decision should reflect the cost of an incorrect business result, not the appeal of writing code.
Octivex's existing Odoo JSON-2 API guide covers deterministic application-to-Odoo calls. JSON-2, Studio webhooks, and custom controllers have different roles: an external API is an interface for explicit operations, while a webhook is a notification or event-triggered mechanism. They can be used together.
Scheduled actions are a recovery tool, not a substitute for event design
Time-based Odoo automation has its own execution semantics. Odoo 20 documents that time-based rules are evaluated when a scheduled check runs; execution is not guaranteed at the exact nominal moment. It also warns that initializing the scheduled-action last-run timestamp matters to prevent unintended historical execution. See the Timing Conditions section.
For integration reliability, a scheduled reconciliation job can compare external orders against Odoo orders over a time window and identify gaps. It should be designed to rerun safely, and it should work in bounded batches instead of scanning the entire history on every invocation.
A reasonable policy might check recently paid orders and outstanding imports, but the exact frequency depends on source-system rate limits, hosting constraints, expected order volume, and required recovery time. Do not promise "instant" synchronization because a cron runs every few minutes.
Test failures before a customer discovers them
An integration acceptance test should prove its behavior under repeat delivery, late delivery, and partial failure—not just a successful first request.
| Test | Expected acceptance evidence |
|---|---|
| Send an identical webhook twice | One resulting business object; delivery log shows the duplicate. |
| Send two different webhook deliveries for the same external order | One Odoo order linked to the external business ID. |
| Retry after Odoo committed an order but the response was lost | Existing Odoo order is reused; no second order or invoice. |
| Deliver an older update after a newer one | Current valid business state is preserved according to the documented source-of-truth rule. |
| Send a payload with an invalid signature | Rejected before modifying Odoo. |
| Remove a required product or tax mapping | Job fails visibly and is reviewable; no silently malformed confirmed order. |
| Stop the receiver temporarily, then restore it | Reconciliation or controlled replay finds the missing work. |
| Process two events simultaneously | The external-ID mapping remains unique. |
| Modify an unrelated Odoo field | The Studio automation does not send a second business notification. |
| Change the active Odoo company | Access checks and company mapping still prevent cross-company writes. |
Run these tests against a staging database with representative modules, access rights, payment and stock configuration, and data volume. No article can substitute for running them on the actual solution.
Troubleshooting a webhook that 'worked' but produced the wrong result
If the source platform reports a successful delivery yet the record is missing in Odoo, look beyond the HTTP status. Check the durable processing state, mapped company, source object ID, and downstream Odoo reference. If Odoo has an order but the external system keeps retrying, investigate where the acknowledgment was lost and whether the operation is idempotent.
If records are duplicated, ask whether the same delivery was processed twice, two different deliveries referred to the same business object, or an Odoo automation triggered a second write. Each failure needs a different fix. An unconditional retry loop is not a diagnosis.
If events are missing only during outages, examine both sides: sender retry policies and your receiver's persistence guarantees. Reconciliation is valuable precisely because no single notification channel should be treated as the only record of business truth.
Choose the smallest design that can be proven correct
Odoo 20 provides useful automation triggers and webhook actions. Use them where they fit. But for orders, payments, invoices, inventory, and identity-sensitive records, define the failure contract first: a stable external reference, permitted state transitions, authentication, idempotency, durable processing state, and a reconciliation path.
A webhook can tell Odoo that something happened. A reliable integration makes sure the right thing happens once, in the right company, with a trail that support teams can explain.
If you are planning an Odoo-to-Shopify, payment, logistics, or mobile integration, Octivex Tech's integration and customization services can help define the event contract, test the failure cases, and decide where native automation ends and custom engineering should begin.
