Overselling happens when two channels sell the same unit before either one learns the other made a sale. The fix is not simply “sync more often.” It is an architecture that decides where inventory truth lives, how much of it each channel is allowed to see, and what happens when an update fails.
This article outlines a design that holds up as you add marketplaces, warehouses and fulfillment models.
Start with a single source of truth
Every channel keeps its own quantity field. None of them should be authoritative. Pick one system to own on-hand and committed quantities, and treat every channel as a subscriber.
For most brands the candidates are:
- ERP (for example, NetSuite) when purchasing, receiving and fulfillment already flow through it.
- Warehouse management system when warehouse transactions are more timely than ERP postings.
- A dedicated inventory service that aggregates ERP, WMS, 3PL and FBA data and publishes channel quantities.
The right choice is the system that learns about physical changes first and most reliably. If your 3PL only posts receipts to the ERP once a day, the ERP cannot be your real-time source, no matter how central it is.
Calculate available-to-promise, not on-hand
Channels should receive an available-to-promise (ATP) number, not raw on-hand. A practical formula:
ATP = on-hand − committed (open orders not yet shipped) − reserved (holds, damaged, QA) − safety stock
Then allocate ATP to channels. Committed quantity is the component most often missed. An order placed on Walmart five minutes ago has not shipped, so on-hand has not changed, but that unit is gone.
Per-channel buffers
Buffers absorb the delay between a sale and the moment every other channel is updated. Size them by channel behavior, not by gut feel.
| Factor | Why it matters | Buffer implication |
|---|---|---|
| Sales velocity | Faster sellers consume more units during sync lag | Larger buffer for high-velocity SKUs |
| Sync latency | Some channels process updates in minutes, others longer | Larger buffer on slower channels |
| Cancellation penalty | Marketplaces track seller-cancel rates and may restrict accounts | Larger buffer where penalties are severe |
| Stock depth | A buffer of 5 on a SKU with 6 units hides most of it | Use percentage buffers or zero-out thresholds for low stock |
| Margin by channel | Scarce units are worth more on higher-margin channels | Allocate last units to preferred channels |
Common patterns include a fixed unit buffer, a percentage of ATP, and a “go to zero” threshold where a channel shows zero once ATP drops below a set level. Many teams combine them: publish 90 percent of ATP, minus two units, and zero out below five.
Event-driven updates: webhooks versus polling
Inventory changes come from two directions: inbound events (orders, cancellations, returns) and internal events (receipts, adjustments, transfers). Your system needs both.
Webhooks and notifications
Where available, subscribe to push notifications. Shopify offers webhooks for orders and inventory levels. Amazon’s SP-API delivers notifications, such as order changes, through Amazon SQS or EventBridge. Push events cut latency and reduce API calls.
Treat webhooks as a hint, not a guarantee:
- Deliveries can arrive late, out of order or more than once.
- Endpoints can be down during a deploy.
- Some platforms remove subscriptions after repeated delivery failures.
Polling as a safety net
Run scheduled polls for recent orders and inventory changes even when webhooks are active. A poll that looks back over a window slightly longer than its interval catches missed events. Use idempotency keys, such as the channel order ID plus line ID, so the same order is never committed twice.
Full reconciliation
Once or twice a day, compare the quantity each channel is displaying against the quantity you intended to publish. Correct drift. This catches manual edits in seller portals, failed feeds and edge cases your event logic did not anticipate.
Respect API rate limits
Each platform throttles differently. Amazon’s SP-API applies per-operation rate limits using a token bucket model, Shopify’s GraphQL Admin API uses a calculated query cost, and Walmart and others publish their own limits. Your sync layer should:
- Queue updates per channel rather than calling APIs inline from the event handler.
- Coalesce changes. If a SKU changes three times in ten seconds, send only the latest quantity.
- Prefer bulk endpoints (feeds or bulk mutations) for large updates, and single-item calls for urgent changes like a SKU hitting zero.
- Prioritize. Updates that reduce quantity, especially to zero, go ahead of increases.
- Back off on throttling responses using the retry headers the API returns, with jitter.
Bundles, kits and virtual SKUs
A bundle has no inventory of its own. Its ATP is derived from its components:
Bundle ATP = minimum across components of (component ATP ÷ quantity required per bundle), rounded down
When a bundle sells, commit each component. That change must then cascade to every other bundle and single-item listing sharing those components. A popular component can appear in many listings, so a single sale may trigger a burst of updates. This is where coalescing and prioritization earn their keep.
Also watch for:
- Multi-packs (a 3-pack of the same item) that are bundles with one component.
- Pre-built kits assembled in the warehouse, which carry real inventory and should be modeled as items with assembly builds, not virtual bundles.
- Channel-specific SKUs mapped to the same internal item. Keep a mapping table with one internal item per channel listing.
FBA versus merchant-fulfilled stock
Inventory held in Amazon fulfillment centers is a separate pool. Amazon manages the available quantity for FBA offers, so you do not push quantities to them the way you do for merchant-fulfilled listings.
Design implications:
- Model FBA as its own location, fed by Amazon inventory reports or API data, and exclude it from ATP for other channels unless you use Multi-Channel Fulfillment.
- Count inbound shipments to FBA as in-transit, not available anywhere.
- If you run the same product as both FBA and merchant-fulfilled offers, use distinct seller SKUs and decide how merchant-fulfilled stock backs up FBA when it runs low.
- If you use Multi-Channel Fulfillment to ship Shopify or Walmart orders from FBA stock, FBA quantity becomes shared inventory and needs its own buffer.
Failure handling
Design for partial failure from the start.
- Dead-letter queues. Updates that fail after retries go to a queue with the error, SKU, channel and intended quantity.
- Fail safe, not fail open. If you cannot compute ATP for a SKU (missing data, stale warehouse feed), publish a conservative value or hold the last known lower value. Never publish an unverified increase.
- Stale source detection. If a warehouse or 3PL feed has not updated within its expected window, reduce exposure on affected SKUs automatically.
- Idempotent writes. Sending “set quantity to 12” is safer than “decrement by 1,” because repeats do not compound.
- Audit trail. Store every published quantity with a timestamp and the inputs used to calculate it.
Monitoring and alerting
Track a small set of signals continuously:
- Sync latency from source event to channel confirmation, by channel.
- Queue depth and dead-letter counts.
- Drift detected during reconciliation, by channel and SKU.
- Seller-cancelled orders attributed to stockouts.
- SKUs published as available while ATP is zero or negative.
- Age of the last successful update from each warehouse, 3PL and FBA feed.
Alerts should route to someone who can act, with enough context (SKU, channel, last known values) to fix the problem without opening five dashboards.
How Audaxim AI helps
Audaxim AI designs and operates inventory sync between NetSuite, warehouses, 3PLs, FBA and marketplaces such as Amazon, Walmart, eBay and Shopify. We implement ATP calculations, channel buffers, bundle logic, rate-limit-aware queues and reconciliation jobs, with monitoring that flags drift before it turns into cancellations.