blog

When WooCommerce Stock Didn’t Reduce After Orders and the Hook Race Condition Fix That Made Inventory Accurate Again

WooCommerce is amazing. It helps people sell products online without needing to be a coding wizard. But sometimes, it acts a little… weird. One such moment is when customers place orders—but the stock number doesn’t go down! That’s exactly what happened, and here’s how we fixed it with a clever hook and some debugging magic.

TL;DR

Orders were being placed, but product stock wasn’t updating. Turns out, it was a timing issue—a race condition with WooCommerce hooks. By reorganizing and custom-coding a hook, stock started updating again. Customers stopped buying “ghost stock,” and everything started working correctly.

The Strange Mystery of the Non-Moving Stock

It started with a simple observation: sales were happening, but the inventory count stayed the same. Uh-oh. That’s a dangerous problem for any online store. If someone buys the last unit of a product, the stock should go to zero. But that wasn’t happening.

Some customers were even able to “buy” products that were already sold out. This led to out-of-stock items being oversold. Refunds had to be issued. Customers were confused. Store owners were frustrated. It was chaos! 😱

How WooCommerce Normally Reduces Stock

When someone places an order, WooCommerce runs its internal functions. One of those functions reduces the stock of the purchased product. It does this through something called an action hook, specifically:

woocommerce_order_status_processing

This hook means “Hey, the order is now processing—time to reduce the stock.”

In theory, this works. But in our case, something else happened.

Enter the Hook Race Condition

Let’s explain “race condition” in simple terms. Imagine two kids racing to press the same elevator button. The one who gets there first decides which floor you’re going to. That’s kind of what happened inside WordPress.

Here’s what we found:

  • Multiple plugins were hooking into the same order status updates.
  • Some were running too early, and others too late.
  • One plugin was marking the stock as reduced… before the WooCommerce default hook even had the chance to run.

The result? WooCommerce said, “Stock’s already updated, no need to do it again.” But…

It never actually happened.

What We Did First (And Why It Didn’t Work)

Our first instinct was to manually reduce the stock ourselves using:

wc_reduce_stock_levels( $order_id );

But that created other problems:

  • Stock got reduced twice for some orders.
  • Refunds didn’t restore stock properly.
  • Logistics and warehouse numbers didn’t match.

It started to get messy really fast. We needed a cleaner fix. Something that worked with WooCommerce—not against it.

Discovering the Root Cause

Using some logging with error_log() and testing plugins one by one, we finally saw it.

One of our custom plugins hooked into the order status change and did some actions, like updating status notes and changing tax rules. While doing that, it also marked the stock as reduced in the order metadata… prematurely. Sneaky!

So, when WooCommerce came in later to do its job, it checked:

if ( $order->get_data()['stock_reduced'] ) return;

Boom. Short-circuited. No stock reduction.

The Fix: Let’s Hook It Right

We needed to take control of the hook sequencing and ensure stock was reduced exactly once and at the right time. Here’s what we did:

  1. We removed all existing stock-reduction logic from custom code.
  2. We made sure our custom plugin did not alter stock_reduced flags.
  3. Then, we added this beautiful piece of code:
add_action( 'woocommerce_order_status_processing', 'custom_stock_reduction_hook', 20 );

function custom_stock_reduction_hook( $order_id ) {
    $order = wc_get_order( $order_id );

    if ( ! $order->get_meta( '_custom_stock_reduced' ) ) {
        wc_maybe_reduce_stock_levels( $order_id );
        $order->update_meta_data( '_custom_stock_reduced', true );
        $order->save();
    }
}

This ensures two things:

  • Stock is only reduced once per order.
  • It reduces after all the early-running plugins finish doing their thing.

Why 20 as Priority?

Hook priority determines when your hook runs. Lower numbers mean earlier, higher numbers mean later. We picked 20 so that our function runs after most plugins that usually use the default 10 priority. It gave other functions time to do their thing before we touched the stock.

This kept things clean—and most importantly, predictable.

Test. Track. Repeat.

After adding the new hook, we started testing everything again. Order paid. Stock reduced. Refund processed. Stock restored. Everything worked as expected.

We watched the logs, checked product counts, and even placed test orders during pretend flash sales. It held up. We were finally back in sync.

Lessons We Learned

  • Race conditions are sneaky. Especially when multiple plugins are involved.
  • Never assume WooCommerce will always reduce stock automatically. Check it with real data.
  • Use priorities when adding hooks. Order matters more than you think!
  • Testing isn’t optional. One working order isn’t enough. Try it 10 different ways with 10 different products.

Wrapping It Up

Fixing WooCommerce stock issues can feel like trying to catch invisible gremlins. But, with patient debugging and a smart use of hooks and priorities, it’s totally doable.

The hook race condition was the villain. Delaying our hook and managing meta flags made everything behave nicely. Now, the store runs smoothly, stock gets reduced accurately, and customers don’t buy what doesn’t exist anymore.

If you’re dealing with stock problems in WooCommerce, maybe a sneaky custom plugin or misbehaving hook is the culprit. Check your priorities!

Happy selling! 🛒🎉