WooCommerce automation

WooCommerce stock sync that scales past 100 orders per day

Why WooCommerce stock sync breaks when orders arrive in bursts, and the event-driven architecture that survives a sale: queues, atomic updates, idempotent handlers and monitoring.

24 May 2026Updated 21 Sept 20264 min readMindScrollers

WooCommerce stock sync rarely breaks on an ordinary day. It breaks when orders arrive in bursts: a sale, a newsletter, a post that takes off. Then the cracks show: oversold SKUs, 504s at checkout, and someone reconciling spreadsheets at midnight after the sale.

The wall is not a database bug. It is an architecture limit. Here is what breaks, and the pattern that fixes it.

What breaks at scale

WooCommerce's default stock model uses synchronous database writes inside the checkout transaction:

  1. Customer adds product to cart.
  2. Customer clicks "Place order".
  3. WooCommerce decrements the SKU's stock in wp_postmeta.
  4. WooCommerce inserts the order in wp_posts / wp_postmeta.
  5. WooCommerce queues the order-completed action.
  6. Customer sees the thank-you page.

With orders spread out, this works fine. When many checkouts arrive in the same minute, three problems emerge:

  • Lock contention. Checkouts for the same products update the same stock rows, so concurrent decrements wait for each other.
  • Worker exhaustion. Each waiting checkout holds a PHP worker and a database connection. Once concurrent checkouts outnumber the PHP workers your hosting gives you, new ones queue, and past the timeout the next checkout fails with a 504.
  • Action Scheduler backlog. WooCommerce's async hooks (emails, webhooks, ERP sync) queue into Action Scheduler. Under load, the queue grows faster than the cron-driven runner can drain it, and downstream systems get hours-old events.

The dashboard does not show any of this until customers complain.

The fix: event-driven sync

Replace the synchronous decrement with an event-driven model:

  1. Checkout writes the order in a single transaction, with stock unchanged.
  2. The order-completed hook emits a webhook to a queue.
  3. A queue worker consumes the event, decrements stock atomically using a database-level operation (UPDATE ... SET stock = stock - ? WHERE stock >= ?), and fires downstream sync.
  4. Failed events get a retry budget with exponential backoff.
  5. A monitor alerts on sync lag exceeding a threshold (60 seconds is a sensible default).

Roll the change out in phases, a small share of traffic first, so a regression can be reverted in minutes.

The atomic UPDATE matters. It prevents the classic race condition where two concurrent checkouts each see 1 unit in stock, both decrement, and the SKU ends up at -1.

UPDATE wp_postmeta
SET meta_value = meta_value - 1
WHERE post_id = ? AND meta_key = '_stock' AND CAST(meta_value AS SIGNED) >= 1

If this returns 0 rows affected, the unit was already taken. The queue worker can either mark the order as oversold (refund flow) or fail-fast back to the customer.

Idempotency: the bug everyone has

The other source of stock-sync chaos is non-idempotent handlers. A handler that decrements stock on every event seen will double-decrement when an event is retried.

Make every handler idempotent:

  • Every event has a unique ID, persisted in a processed_events table.
  • Handler checks the table on entry. If the event ID is present, return immediately.
  • After processing, insert the event ID into the table inside the same transaction as the state change.

This pattern is mechanical and boring. Every retry-safe system in production looks like this.

The fulfilment side

Stock sync is half the problem. The other half is fulfilment: vouchers, tracking, courier callbacks.

Pattern:

  • Order completed → webhook to the fulfilment provider or courier (UPS, FedEx or DHL; in Greece ELTA, ACS, Speedex or Box Now).
  • Provider returns voucher ID and tracking URL synchronously.
  • Order updated with voucher ID and tracking URL.
  • Customer notified via email and (optionally) SMS / Viber with the tracking link.

The whole loop should complete in under 60 seconds for the customer experience to feel "instant". With a properly tuned queue, even at peak volume this is achievable.

ERP integration

When a store connects an ERP, such as SAP Business One, NetSuite or Odoo, or Softone, Entersoft or Megasoft in Greece, the integration pattern is:

  • WooCommerce remains the source of truth for the product catalog and the order events.
  • ERP is the source of truth for accounting, stock at the warehouse level (vs. the storefront level), and invoicing.
  • A bidirectional sync runs every 5-15 minutes. Stock-at-warehouse flows down to WooCommerce as a clamp on storefront stock. Orders flow up to the ERP for invoicing.

Avoid making the ERP the source of truth for storefront stock. ERP systems are not designed for real-time response to web traffic, and a stock change made in the ERP only reaches the storefront at the next sync, which is forever during a sale.

Monitoring

Without monitoring, none of this is visible until a customer complains. The dashboards to have:

  • Queue depth. How many unprocessed events are in flight. Alert when sustained above N (depends on volume).
  • Sync lag. Time from order completion to fulfilment-system acknowledgment. Alert above 60 seconds.
  • Oversold count. Per day. Alert immediately if non-zero.
  • Webhook failure rate. Per minute. Alert above 1% sustained.

Without these, you find out about the bug from a customer email three days later.

What scaling actually looks like

With this architecture, stock sync stops being the limit. The next bottlenecks move to the front end (checkout page load times, payment gateway latency, image delivery) and the database (wp_postmeta for high-cardinality stores; consider a custom orders table per WooCommerce's HPOS migration). Measure before you go there: fix the sync layer first and see where the next limit really is.

What this looks like with us

How we build writes the order flow into the specification before the build: where stock is written, what is queued, and which handlers must be idempotent.

If you are starting a store, the online store is one of the builds in Startup, the one package we sell, with stock held at order and orders routed to fulfilment, and the price and the timeline published.

Questions

What readers ask.

Why does WooCommerce stock sync break at high volume?

The default WooCommerce stock model writes stock inside the checkout request. With a steady trickle of orders that is fine. When many checkouts land in the same minute, as in a sale, they wait on the same rows and on the PHP workers the host gives you, and the slowest ones time out. Symptoms: 504s at checkout, oversold SKUs, manual reconciliation work.

Do we need to replace WooCommerce to scale?

Rarely. Fix the sync layer first: most of the pain described here comes from how stock is written, not from WooCommerce itself. A platform replacement is a long detour if the actual bottleneck is the checkout write.

Which stock-sync bug does the most damage?

Webhook handlers that are not idempotent. A retry from the courier API, an ERP that double-sends, or a flaky network all produce duplicate events. Handlers that decrement stock on every event seen, without checking whether they have already processed it, oversell on every retry.