Until this release, Shopify inventory landed in the WMS via a polling job that ran every 30 minutes and overwrote the on-hand count for each SKU with whatever Shopify reported. If a picker had just spotted a miscounted bin and an operator had corrected the WMS to 23, the next sync could blindly put it back to 25 because Shopify hadn't caught up yet. The WMS couldn't be the source of truth — it was always trailing.
This release flips that.
The new flow, in two sentences
Shopify now pushes inventory changes to the WMS the moment they happen. When a change arrives, the WMS compares what Shopify says to what the WMS already has, and only applies the difference — never restating the full quantity, never overwriting a per-location correction someone made by hand.
What changes on each event
Shopify sends us a number; we already have a number. Three things can be true:
- The numbers match → we do nothing. Logged for the trail, but no inventory movement.
- Shopify is higher than the WMS (someone added stock somewhere outside the WMS, or we missed a receive) → we add the difference to the primary location for that product. Single, surgical bump.
- Shopify is lower than the WMS (a sale we hadn't yet seen, or external adjustment) → we find the first location holding enough stock to absorb the subtraction and remove the difference from there. If no single location holds enough, we skip and flag the case in the activity log so operators can audit it per-location — better to do nothing than to push a location negative behind everyone's back.
Every applied delta writes one inventory adjustment row and one "Inventory Adjusted — Shopify Delta" entry on the activity log with the math: "Shopify reported 280, WMS had 273, delta +7 added to primary location G-2-3." When the safety guard trips you'll see a "Shopify Delta Skipped — No Location Holds Enough" entry instead, with the SKU and the numbers, so it goes straight on the audit pile.

Why this matters in practice
The WMS now owns inventory accuracy. Shopify nudges totals when they drift; operators correct per-location detail via pick exception audits. The override behavior is gone — manual on-hand corrections finally stick.
For the warehouse floor: when someone discovers a count was off and the WMS gets corrected, the next Shopify update doesn't blow that correction away. The system holds the line the operator just drew.
Settings → Webhooks page got tidier
We've been adding webhook integrations steadily (Shopify orders, Duoplane POs, ShipStation shipments, now Shopify inventory) and the flat 2-column list was getting hard to scan. The Webhook Endpoints tab now groups by source — Shopify, Duoplane, ShipStation each get their own section with a count badge and a short note about where to configure them on the source side.

Two brand-new Shopify endpoints appear here:
- Inventory Item Updated — receives
inventory_items/createandinventory_items/update. Maintains the SKU mapping that powers the delta sync. - Inventory Levels Update — receives
inventory_levels/update. This is the one doing the real-time delta math above.
Copy the URL from the card and paste it into the matching subscription on the Shopify side. Both work out of the box once Shopify is wired up.
Security note: Shopify webhook signatures are now verified
Quietly important: all Shopify webhooks (the new inventory ones plus the existing order-created / order-cancelled endpoints) now verify Shopify's HMAC signature against the app webhook secret. Requests without a valid signature get rejected with a 401 instead of being silently processed. This was an open hole before — closed now.
If SHOPIFY_WEBHOOK_SECRET isn't set on a given environment (e.g. local dev that hasn't wired up Shopify), the middleware logs a warning and lets the request through, so existing dev workflows still work.
What's coming next
The legacy 30-minute Shopify puller (the n8n-driven Sync Connection) is still running as a safety net while we verify the webhook flow on production. It currently does the old override behavior — that's intentional for now, and it also bootstraps the SKU mapping for any items that haven't fired a webhook yet. A follow-up will swap it to use the same delta logic as the webhook, so the override behavior is fully retired across both paths.