Summarize This Article With AI
A customer pays for the last unit of a blue size-M hoodie at 9:14. At 9:15 a second customer buys the same unit, because the storefront still shows one in stock. Odoo knew about the first sale, but WooCommerce hadn’t heard yet. Now someone on your team writes an apology email and issues a refund.
That gap is what most overselling looks like when WooCommerce runs the storefront and Odoo runs the back office. The connector rarely breaks outright. The problem is that nobody wrote down which system owns stock, which quantity gets published, and what happens in the minutes between a sale and the next sync.
Quick answer: make Odoo the single owner of stock, publish its free-to-use quantity (not on-hand) minus a per-SKU buffer, push changes on events rather than only on a timer, import orders the moment they’re placed, and alert on any sync job that stops running.
1. Why Overselling Happens Between Odoo and WooCommerce
Overselling is a timing and definition problem. Two systems each hold a stock number, and for some window they disagree. Any sale inside that window can sell a unit that no longer exists.
The usual causes stack on top of each other:
- Sync latency. A job that runs every 15 minutes leaves a 15-minute window after every sale. On a busy SKU, that’s enough for several duplicate orders.
- The wrong quantity. Odoo tracks more than one number per product. Its Inventory documentation defines Free to Use as the on-hand quantity not reserved for delivery or manufacturing orders, which makes it different from On Hand. If your connector publishes On Hand, every unit already reserved for a confirmed sales order is still for sale online.
- Forecasted stock leaking in. Forecasted quantity includes incoming purchase orders. Publishing it means selling stock that’s still on a truck, which is fine only if you’ve decided to accept backorders on purpose.
- Multi-warehouse blind spots. If Odoo has three warehouses and only one ships web orders, a connector that sums all locations will promise stock the web channel can’t touch.
- Kits, bundles, and variants. A gift box built from three components has no stock of its own. Its availability is the lowest component count, and a connector that doesn’t compute that will either show the bundle as always in stock or never in stock.
The cost isn’t trivial at scale. IHL Group’s 2025 research put global retail losses from inventory distortion at about $1.77 trillion, with roughly $1.2 trillion of that from out-of-stocks. Overselling is the online version of the same failure: you promised something you didn’t have.
2. Decide the Source of Truth Before You Pick a Connector
Before anyone installs anything, write one sentence: “Odoo owns stock, WooCommerce owns the cart.” Every sync rule in the rest of this article follows from it.
Odoo should own stock because it’s where receipts, transfers, manufacturing, and adjustments actually happen. WooCommerce should still decrement its own count when an order is placed, since that’s how it stops a second buyer in the same second. But WooCommerce’s number is a local cache. When the next sync arrives from Odoo, Odoo’s number wins, without exception.
Two-way stock sync, where either side can overwrite the other, causes the most damage. A manual edit in the WooCommerce admin overwrites Odoo, Odoo pushes its value back, and you end up debugging which change happened last. The fix is a process change: lock stock fields in the store admin and make adjustments in Odoo.
Once ownership is settled, the connector choice gets easier. Prebuilt connectors from the Odoo Apps store cover the common path well: product and stock export, order import, customer creation, and basic status updates. They tend to struggle with business rules that live outside a settings screen, such as different buffers per product category, kits that span warehouses, or reserving stock in Odoo before payment is captured. If your rules fit what a connector exposes as configuration, a connector is the cheaper and faster choice. If they don’t, that’s usually the point where a team scopes custom middleware, whether in-house or through outside odoo integration services, and the decision should rest on how many of your rules the off-the-shelf option can’t express.
3. Stock Sync Rules: Direction, Frequency, and Safety Buffers
With Odoo as the owner, stock flows one way: Odoo to WooCommerce. Here are the rules that keep that flow honest.
Push on Events, Sweep on a Schedule
Event-driven pushes are what shrink the oversell window. When a stock move is validated in Odoo, a delivery is confirmed, or a sales order reserves stock, the affected SKUs should be queued and pushed within seconds. That’s the fast path.
The slow path is a full reconciliation job that runs on a schedule, perhaps hourly or nightly depending on catalog size. It compares every SKU and corrects drift that the event path missed. You need both. Events alone will eventually miss one (a crashed worker, a timeout), and a schedule alone leaves wide windows on fast sellers.
Publish One Defined Number
Pick the formula and document it. A sensible default is Free to Use in the warehouses assigned to web fulfillment, minus a buffer. Don’t let the connector decide for you, and check what “available” means in its settings, because some tools default to On Hand.
Use Buffers per SKU, Not One Global Number
A buffer is the quantity you hold back from the storefront to absorb sync delay and counting errors. A global buffer of five units hides your entire stock on low-volume items and does nothing for a SKU that sells 40 a day. Set buffers by velocity or by category:
- Fast movers and promo items get a larger buffer during campaigns.
- Items with frequent counting errors (small parts, loose items) get a buffer tied to their historical variance.
- Made-to-order or dropship items get no buffer, because they’re not limited by shelf stock.
- Kits get a buffer on the computed kit quantity, not on each component separately.
Treat SKU as the Join Key
Every record that crosses between systems should match on SKU, and SKUs must be unique and identical on both sides. Variants need their own SKUs in WooCommerce that map one-to-one to Odoo product variants. Matching on product name or on WooCommerce’s internal ID is how two products end up sharing one stock level after someone renames a listing. Run a duplicate-SKU check in both systems before the first sync and block any record without one.
Respect the API’s Capacity
In practice, the request limit you hit with the WooCommerce REST API is usually your hosting stack: PHP workers, a web application firewall, or a managed host’s throttling. Use the batch endpoints, send only SKUs that changed, and back off on 429 or 5xx responses instead of retrying in a tight loop. A full catalog push at noon on a sale day will slow your own checkout.
4. Order Import Rules: Timing, State Mapping, and Returns
Stock accuracy depends just as much on how fast orders reach Odoo. Until an order exists in Odoo, Odoo can’t reserve stock for it, and its Free to Use number stays too high.
Import on Creation, Not on Fulfillment
WooCommerce can notify your middleware the moment an order is created through its order.created webhook topic. Import the order immediately and confirm it in Odoo so the reservation happens. For teams building their own order-import pipeline, Odoo API integration explains how Odoo exchanges records with external systems. A connector that pulls orders every 30 minutes keeps your Odoo stock overstated for up to 30 minutes after every sale.
Be aware of how WooCommerce sends those notifications. Its webhook documentation notes that delivery is processed in the background by default using wp-cron, which means delivery depends on site traffic or a real server cron triggering it. On a quiet store at 3 a.m., “instant” can mean several minutes. Pair webhooks with a scheduled pull that picks up any order created since the last successful import.
Map States Explicitly
| WooCommerce status | Odoo action | Stock effect |
|---|---|---|
| Pending payment | Create quotation or skip, per policy | None, or soft hold |
| Processing / On hold | Confirm sales order | Reserves stock |
| Completed | Validate delivery if not already done | Reduces on-hand |
| Cancelled | Cancel sales order | Releases reservation |
| Refunded | Create return only if goods come back | Restock after inspection |
| Failed | Cancel or delete quotation | Releases any hold |
Write a table that maps each WooCommerce status to an Odoo action, and don’t leave gaps:
The refunded row trips up a lot of teams. A refund is money, not stock. Restock only when the item is physically back and inspected; otherwise a refunded-but-lost parcel puts a phantom unit back on sale.
Handle Partial Shipments in Odoo
If Odoo splits a delivery into a backorder, WooCommerce doesn’t need to know about the split for stock purposes, because the reservation stays in Odoo. It does need a status or note for the customer. Push tracking per shipment, and keep the WooCommerce order in Processing until the last part ships.
5. Sync Approaches Compared
Most setups fall into one of four patterns. The trade-off is between latency, operational effort, and how much can go wrong silently.
| Approach | Typical latency | Oversell risk | Failure mode | Good fit |
|---|---|---|---|---|
| Scheduled batch only | Minutes to hours | High on fast movers | Wide windows after every sale | Small catalogs, low daily volume |
| Webhooks + scheduled reconciliation | Seconds to minutes | Low | Missed events caught by the sweep | Most stores running Odoo as the back office |
| Real-time stock check at checkout | Near zero | Very low | Checkout slows or fails if Odoo is down | High-value or very limited stock |
| Two-way sync, either side writes | Varies | High | Overwrites and loops | Rarely a good choice |
Most stores should land on the second row. The third pays off only when a single oversell is expensive, such as limited drops, and you’ve decided what checkout does when Odoo doesn’t answer.
6. Go-Live Testing Checklist
Test against a staging copy of both systems with real SKUs, not a demo catalog. Before launch, confirm each of these:
- Two buyers checking out the last unit at the same time: only one order succeeds, or the second goes to backorder by design.
- A sale in WooCommerce reduces Free to Use in Odoo within your target window.
- A receipt validated in Odoo raises stock in WooCommerce within the same window.
- A kit’s storefront quantity drops when a single component sells through another channel.
- Stock from a non-web warehouse never appears on the storefront.
- A cancelled order releases its reservation in Odoo and restores WooCommerce stock once, not twice.
- A refund without a return leaves stock unchanged.
- A product with a missing or duplicate SKU is rejected and reported, not silently skipped.
- Stopping the middleware for an hour, then restarting it, leaves both systems matching after the next reconciliation run.
- A bulk price or catalog update doesn’t push stock values that weren’t meant to change.
Teams often skip the restart test, yet it’s the one that matches real life: workers crash and API keys expire.
7. Monitoring and Alerts After Launch
A sync that stops quietly is worse than one that throws errors. WooCommerce’s own webhooks show why: the same documentation says a webhook that fails five consecutive deliveries is disabled and must be edited through the REST API to re-enable, and only the 25 most recent delivery logs are kept. If your middleware goes down for an afternoon, WooCommerce may stop sending orders entirely, and the logs that explain why may already be gone.
Set alerts for the conditions that actually precede overselling:
- Last successful sync age. Alert if stock or order sync hasn’t succeeded in, say, 10 minutes during trading hours.
- Webhook status. Check that each WooCommerce webhook is still active, on a schedule, and alert if one flips to disabled.
- Drift count. After each reconciliation, count SKUs where the two systems disagreed. A sudden jump means the event path is failing.
- Unmatched records. Orders that reference a SKU Odoo doesn’t know should land in a queue someone owns, not in a log file.
- Negative Free to Use. Any SKU that goes negative in Odoo is an oversell that already happened. Review each one and adjust its buffer.
Route alerts to a channel your operations team watches, not only a developer inbox. The person who can pause a promotion is often the one you need first.
A small SaaS team may begin with an OpenAPI contract, automated compatibility checks, and one accountable API owner. As consumer count and integration risk grow, formalize the review, communication, support, and retirement practices.
Frequently Asked Questions
Should WooCommerce or Odoo be the source of truth for stock?
Odoo, in almost every setup where Odoo manages purchasing and warehouses. WooCommerce still decrements stock locally at checkout, but Odoo’s published number overwrites it on every sync.
Which Odoo quantity should I send to WooCommerce?
Free to Use from the warehouses that fulfill web orders, minus a per-SKU buffer. On Hand ignores reservations, and Forecasted includes stock you haven’t received yet.
How often should stock sync run?
Push changes on events within seconds, then run a full reconciliation on a schedule, hourly or nightly depending on catalog size.
Are WooCommerce webhooks enough on their own?
No. They’re delivered in the background and can be disabled after repeated failures. Use them for speed and back them with a scheduled pull of new orders.
How do I handle bundles and kits?
Compute the kit’s available quantity from its components in Odoo, publish that computed number, and apply the buffer to the result. Never give a kit independent stock in WooCommerce.
Do I need custom middleware, or will an Odoo Apps connector work?
Start by listing your sync rules. If a connector can express all of them as configuration, use it. If you need per-category buffers, cross-warehouse kits, or a stock check at checkout, custom work is usually cheaper than fighting the connector’s limits.
Conclusion
Overselling between Odoo and WooCommerce comes from undefined rules more often than from broken software. Three changes cover most of the risk. First, make Odoo the only writer of stock and publish Free to Use from web warehouses, minus a buffer sized per SKU. Second, import orders on creation and map every WooCommerce status to a specific Odoo action, including refunds that shouldn’t restock. Third, monitor the sync itself: alert on stale jobs, disabled webhooks, and drift. Write these rules down this week, before you change a connector setting.