Blog
How to Sync Inventory Across Sales Channels: A 2026 Operator's Guide
A 2026 operator guide to multichannel inventory sync: system of record, per-channel buffers, webhooks vs polling, marketplace limits, reconciliation and rollout.

Syncing inventory across sales channels comes down to one rule: keep one system responsible for each number, and treat every quantity a channel displays as a derived value that can be recomputed at any time. On-hand stock belongs to whoever physically holds the goods (your warehouse system or 3PL). Committed stock and available-to-sell belong to your commerce platform. The quantity published to Amazon, eBay, TikTok Shop, or a POS terminal is calculated from those two, minus a buffer, and is never stored anywhere as the truth.
Most sync failures come from two systems believing they own the same number, from absolute overwrites that erase sales made in between, and from events arriving out of order. This guide walks through the data model, the sync mechanics, per-channel buffers, oversell handling, reconciliation, and a staged rollout. Where it maps onto Swell, it only describes stock fields, events, and webhook behavior documented in Swell's developer docs.
This is a guide about keeping stock correct over weeks and months. If your concern is surviving a traffic spike (concurrent checkouts on the last units, webhook backlogs under load), that is covered in How Checkout Performance Wins BFCM, and this post treats those problems as solved.
Key Takeaways
- Assign a system of record to each number: on-hand to the WMS or 3PL, committed and available-to-sell to commerce, channel-published quantity to nobody (it is derived).
- Use one formula everywhere: available = on-hand minus committed minus buffer, with the buffer set per channel.
- Reservation timing is a per-channel policy. Web, POS, marketplace, and B2B quotes each commit stock at a different moment.
- Send deltas (adjustments) between systems wherever possible, and reserve absolute overwrites for scheduled reconciliation against a trusted count.
- Webhooks give speed, polling gives completeness. Run both, and design for events that arrive late, twice, or out of order.
- Size each channel's buffer by its sync latency and by the penalty for overselling there. A marketplace cancellation costs more than a web apology email.
- Reconcile on a schedule, write corrections as reason-coded adjustments, and alert when drift crosses a threshold.
- Roll out one channel at a time, read-only first.
Step 1: Decide Which System Owns Each Number
Before touching any integration, write down the numbers you track and the one system allowed to change each. A typical multichannel stack has four candidates: a WMS or 3PL portal, the commerce platform, one or more marketplaces, and a POS.
On-hand is the physical count of sellable units in a location. The system that receives, picks, and counts goods owns it. If a 3PL ships your orders, its WMS owns on-hand for its building, and your platform should accept its counts rather than argue with them. If you ship from your own stockroom with no WMS, the commerce platform can own on-hand for that location.
Committed (sometimes called allocated or reserved) is stock promised to orders that have not shipped yet. Commerce owns it because commerce is where orders from every channel land.
Available-to-sell is on-hand minus committed. Commerce computes it because it is the only system that sees both inputs.
Channel-published quantity is what Amazon, eBay, TikTok Shop, or your storefront shows a buyer. It is available-to-sell minus that channel's buffer, sometimes capped. Never treat a marketplace's displayed quantity as an input. If you read a number back from a marketplace and write it into your platform, you have created a second owner, and the two will drift.
Write this ownership map down and share it with whoever configures the 3PL connector, the marketplace app, and the POS. Most "phantom stock" incidents trace back to one of those tools being configured to push quantities in both directions.
Step 2: Use Three Numbers and One Formula
The working formula is:
available-to-publish (per channel) = on-hand minus committed minus channel buffer
Compute it per location if you have more than one, summing only the locations that fulfill for that channel. Floor the result at zero before publishing: negative available is a valid internal state (you are oversold), but no channel should receive it. And keep the buffer outside the stored stock. If you lower on-hand to "hide" safety stock, a reconciliation job will later see a mismatch with the WMS and "fix" it, wiping your buffer. Buffers are a publishing rule, not an inventory adjustment.
How this maps to Swell
Swell's product model exposes the fields you need for the commerce side of the formula:
- `stock_tracking` turns stock tracking on for a product.
- `stock_level` is the quantity currently in stock across all variants, "based on the sum of the stock entries."
- `stock_status` is computed and takes the values `in_stock`, `out_of_stock`, `backorder`, `preorder`, and `discontinued`.
- `stock_purchasable`, `stock_backorder`, and `stock_preorder` control whether an order can still be placed when stock runs out. Per the docs, when `stock_purchasable` is false, order submission is blocked unless the stock status allows ordering.
- `stock_locations` holds stock distributed over inventory locations "if multi-location inventory is enabled," and `available_locations` lists the custom locations for a product.
- `stock` is an expandable list of the stock adjustments behind the level.
That last point matters. In Swell, the stock level is the sum of a ledger of adjustments, which is the delta-first model this guide recommends. The stock adjustment API creates entries with `POST /products:stock`. Each entry has a required `quantity` (positive increases stock, negative decreases it), the resulting `level`, an optional `variant_id` and `location`, an `order_id` when the change came from a sale or a cancellation, and a `reason` from a fixed list: `received`, `returned`, `canceled`, `sold`, `missing`, `damaged`, `transfer_remove`, and `transfer_add`. There is also a free-text `reason_message`. You can list adjustments with `GET /products:stock`, filtered and sorted, which is useful for audits.
The buffer and the per-channel publish calculation are not product fields. They live in your integration layer, which reads `stock_level` (or per-location stock) and applies the channel rules.
Step 3: Set a Reservation Policy for Each Channel
When stock moves from available to committed is a policy decision, and the right answer differs by channel. Concurrency on the web checkout (two buyers, one unit) is covered in the checkout performance guide linked above. Here the question is simply which event commits stock for each channel.
Web storefront. Commit when the order is placed or paid, depending on your payment flow. Pick one and make sure every downstream system uses the same trigger. In Swell, the events list includes `order.created`, `order.submitted`, and `order.paid`, so you can choose which event your sync layer treats as the commit point.
POS. A POS sale is an immediate, physical decrement. The customer walks out with the item. Treat a POS sale as both committed and fulfilled at once, at the store location. The hard part is offline mode, covered below.
Marketplaces. A marketplace order arrives already placed and paid on the marketplace side. Commit on import, as early as you can pull or receive it. The gap between the buyer clicking buy on Amazon and your system knowing about it is the window in which you can oversell elsewhere, so shrinking it is most of the marketplace work.
B2B quotes. A quote is a soft hold. Decide whether quoted stock counts as committed, and if it does, give the hold an expiry date so abandoned quotes release stock automatically. If wholesale volume is a large share of sales, the B2B wholesale platform roundup compares platforms' wholesale features.
Preorders and backorders. These deliberately allow committed to exceed on-hand. Keep them out of the marketplace feed unless the marketplace supports them explicitly, and use status fields (in Swell, `stock_preorder` and `stock_backorder`) so the web store can keep selling while the published marketplace quantity stays at zero.
Step 4: Build the Sync Mechanics
Prefer deltas over absolute overwrites
There are two ways to tell another system about stock. An absolute update says "the quantity is now 12." A delta says "3 were sold" or "20 were received."
Absolute updates are simple, but they lose information under concurrency. Suppose the WMS reads 15 units, a marketplace order and a web order each take one, and then the WMS pushes "15" on its next cycle. The two sales are erased, and you are oversold by two. Deltas do not have this problem: "minus 1, minus 1, plus 20" lands correctly in any order as long as each is applied exactly once.
Square's inventory API makes the same distinction. It separates an `InventoryAdjustment`, which moves a quantity from one state to another, from an `InventoryPhysicalCount`, which records "the verified quantity of an item variation at a location" from a manual count or trusted system (Square Inventory API overview). That split is a good default for any integration: adjustments for day-to-day movement, absolute counts only when a trusted count happens.
Some channels only accept absolute quantities outbound. TikTok Shop's Update Inventory endpoint takes a `quantity` per SKU per `warehouse_id`, and its error list notes that when a product is stocked in multiple warehouses, all of them must be included in the request. That is fine, because the channel-published quantity is a derived value you recompute from your own ledger. The rule is: deltas inbound into the system of record, absolutes outbound to channels that require them.
Make every write idempotent
The basic webhook-handling pattern (acknowledge fast, queue, process asynchronously, dedupe) is laid out in the checkout performance guide linked above. For stock, key each ledger write on the source system's ID for the movement (the marketplace order line, the WMS receipt line, or the POS transaction ID), the way Square's batch endpoint takes an `idempotency_key` per request and accepts up to 100 changes per call (BatchChangeInventory reference).
Handle events that arrive out of order
Do not assume events arrive in the order they happened. Swell's webhook documentation does not state an ordering guarantee, and retries alone can reorder deliveries: if one delivery fails and is retried a minute later, events created after it may already have arrived.
Three techniques handle this:
- Apply deltas, not states. If each event means "minus 1 for order X," order does not matter. This is the main reason to prefer deltas.
- Compare timestamps or versions for state updates. When an event carries a full state (a product record, a status), store the source timestamp of the last state you applied and ignore anything older. Swell events carry `date_created`, which is suitable for this comparison.
- Re-read on doubt. When an event looks stale or conflicting, fetch the current record from the source API instead of trusting the payload. A webhook then becomes a "something changed, go look" signal, which is the most robust pattern for stock.
Use webhooks for speed and polling as a backstop
Webhooks get changes to you in seconds. Polling guarantees you eventually see every change, even if a webhook endpoint was down for a day. Run both.
Swell's webhook contract, per the same docs:
- Requests time out after 10 seconds unless otherwise specified, and your endpoint must return a `2xx` status.
- A failed delivery is retried after 1 minute, then with exponential back-off, up to 10 attempts a day. After every 10th failed attempt, the next retry is 12 hours later.
- Responding with `410`, or with a non-2xx status and a JSON body containing `"retry": false`, stops retries.
- If an endpoint keeps failing for 4 days without a successful delivery, the webhook is disabled until re-enabled, and admins are notified.
For stock, the relevant events from Swell's events reference are `products.stock_adjusted` ("Occurs when product stock details change"), `products.variant.updated`, and the order events `order.created`, `order.submitted`, `order.paid`, `order.canceled`, and `order.delivered`. Events can also be listed through `GET /events` with filtering and sorting, which gives you the polling backstop without building your own change log: on a schedule, list events since your last checkpoint and process any your webhook handler has not seen.
Respect marketplace rate limits and latency
Marketplace APIs are throttled, and that sets a floor on how fresh your published quantities can be.
Amazon. The Selling Partner API uses a token bucket: tokens refill at a set rate up to a burst maximum, and requests beyond that get a `429`, which Amazon describes as retryable but requiring back-off (usage plans and rate limits). For the Listings Items API, `patchListingsItem` is limited to 5 requests per second per account-application pair with a burst of 5 (Listings Items API rate limits). On the order side, `getOrders` in Orders API v0 allows 0.0167 requests per second, roughly one per minute, with a burst of 20. The newer Orders API v2026-01-01 `searchOrders` operation is limited to 0.0056 requests per second (roughly one every three minutes) with a burst of 20 (Orders API rate limits). If you poll for new Amazon orders, a gap of one to three minutes between polls, depending on API version, is built into the design, which feeds directly into the buffer you need on other channels.
eBay. The Inventory API's `bulkUpdatePriceQuantity` call updates up to 25 inventory item records and their active offers per request (eBay bulk updates). With a large catalog, batch changed SKUs into groups of 25 and push only SKUs whose published quantity actually changed.
TikTok Shop. Inventory updates go per product, with quantities per SKU and per warehouse, and all warehouses that stock a product must be included (Update Inventory). Check the current rate limits in TikTok's partner documentation for your app before sizing a sync job.
Two rules follow: push only quantities that changed since the last successful push, and push "changed to zero" first, because those updates prevent oversells.
Step 5: Set Safety Stock by Channel
A buffer covers the gap between a sale happening on one channel and every other channel finding out. Size it with two inputs.
Sync latency. How many seconds or minutes pass between a sale on channel A and the reduced quantity reaching channel B? Measure it by logging the source order timestamp against the outbound push timestamp. A channel that is polled once a minute, or a feed that is processed in batches, needs a bigger buffer than a storefront reading the platform directly.
Oversell penalty. What happens if you sell a unit you do not have?
- On your own web store, the cost is an apology email, a refund or backorder offer, and some goodwill.
- On Amazon, seller-initiated cancellations count against your cancellation rate. Amazon advises keeping it "below 2.5% to prevent deactivation" (Amazon selling policies).
- On eBay, a seller cancelling because an item was out of stock counts as a transaction defect, and the standard is no more than 2% of transactions, with defects from more than four different buyers needed to fall below standard (eBay seller standards policy).
A practical buffer policy:
- Web store: zero or near-zero buffer. It reads the platform directly and the oversell cost is lowest.
- POS: the store's own location stock, no buffer, but the store's units should not be published to marketplaces unless you ship from stores.
- Marketplaces: a fixed unit buffer per SKU, larger for fast movers, and a rule that publishes zero when available falls below a threshold. For a SKU with three units left, it is usually better to sell them on the web store than risk a marketplace cancellation.
- B2B: hold back wholesale allocations explicitly instead of letting wholesale orders consume retail stock. Wholesale-specific planning (reorder points, counting programs) is a separate topic; see the wholesale inventory statistics roundup for context.
Revisit buffers monthly using your oversell log (Step 6). Months of zero oversells behind a large buffer means you are hiding sellable stock.
Step 6: Have an Oversell Playbook Ready
Even a well-built sync oversells occasionally: a miscount, a damaged unit found at pick time, a POS that was offline. Decide in advance what happens.
- Detect. Flag any order line that pushes available below zero for its location. In Swell terms, watch `stock_level_min` (documented as the minimum stock value across variants, which may be negative) or the variant's own stock level, or check the `level` on new stock adjustments (`GET /products:stock`) after a `products.stock_adjusted` event.
- Choose which order loses. Protect the channel with the highest penalty. If a marketplace order and a web order compete for the last unit, usually ship the marketplace order.
- Look for stock elsewhere. Check other locations and inbound receipts before cancelling. A split shipment or a short delay often beats a cancellation, especially on marketplaces that count cancellations against you.
- Message the customer quickly. Offer a backorder date, an alternative, or a refund before the customer asks.
- Record the cause. Log every oversell with SKU, channels involved, and root cause (sync delay, miscount, offline POS, manual edit). This log is how you tune buffers and find broken integrations.
When you cancel, make sure the cancellation releases committed stock through the same path as any other cancellation. In Swell, stock adjustments can carry the `canceled` reason and the related `order_id`, which keeps the ledger traceable.
Step 7: Integrate the WMS or 3PL
The WMS or 3PL owns on-hand for its building, so the integration has a clear direction for each flow. (For choosing between in-house, 3PL, and hybrid fulfillment, and how order handoff works, see the subscription box fulfillment guide.)
Receiving. When a purchase order or transfer is expected, the WMS usually receives an advance ship notice (ASN). Do not add stock to available when the ASN is created. Add it when the WMS confirms receipt, and write it as a `received` adjustment with the receipt reference. If you want to sell inbound stock early, do it as a preorder or backorder status, not as on-hand.
Shipping. Committed stock leaves on-hand when the WMS confirms shipment. Make sure the platform does not decrement twice: once when the order is placed and again when the 3PL reports the shipment. In Swell, sale and cancellation adjustments are created automatically when stock tracking is enabled, so the 3PL shipment confirmation should only update fulfillment status and must not post a second `sold` adjustment.
Damage, shrink, and returns. The WMS reports these as adjustments with reasons (`damaged`, `missing`, `returned` in Swell's reason list). Returned units should only become available after inspection, which the WMS decides.
Transfers. Moving stock between locations is two adjustments that must both succeed. Swell models this with `transfer_remove` and `transfer_add` reasons and a `transfer_location` field.
Reconciliation direction. When the WMS and commerce disagree on on-hand, the WMS wins, unless you have evidence the WMS missed a movement (an order shipped from a different location, for example). That is the whole point of naming a system of record.
Step 8: Handle POS and Offline Mode
Store stock should be its own location in your inventory model, so a store sale reduces that store's count and does not touch warehouse stock that feeds the web and marketplaces.
The hard part is offline selling. Many POS systems keep taking payments without a connection and sync later. Square, for example, processes offline payments automatically once the device reconnects, and requires the upload within 72 hours of the start of the offline session (it recommends 24 hours) (Square offline payments help). That can be up to three days of sales your central system does not know about.
Plan for it:
- Do not publish store-location stock to online channels unless you ship from stores. Then offline sales can only oversell the store itself.
- If you do ship from stores, add a store-specific buffer, and stop allocating online orders to a store whose POS has not checked in recently.
- When offline sales upload, apply them as deltas with the POS transaction ID as the idempotency key. Never let a reconnecting POS push an absolute count it computed while offline.
For research on store and online integration, see the POS integration statistics roundup.
Step 9: Run Reconciliation Jobs
Even with idempotent deltas and a polling backstop, drift creeps in through manual edits, missed events, and bugs.
Hourly: commerce against channels. For each channel, compare the quantity you last published with what you would publish now, and re-push any difference. This catches failed pushes and throttled requests that were never retried.
Nightly: WMS against commerce. Pull on-hand per SKU per location from the WMS. Compute what commerce believes on-hand is (stock level plus committed-but-not-shipped, depending on how your platform counts). For any difference:
- Write a corrective adjustment, not an overwrite. Use a reason code: in Swell, `missing` for shortfalls, and for surpluses `received` with a `reason_message` prefixed by the reconciliation run ID (for example `RECON-2026-10-05`), so audits can separate corrections from real receipts.
- Skip SKUs with orders in flight between the two systems, or compare against a snapshot timestamp, so you do not "correct" a sale that just has not reached the WMS yet.
- Alert when the correction for a SKU exceeds a threshold, or when the number of SKUs needing correction jumps. A sudden spike usually means an integration broke, not that the warehouse lost stock.
Weekly: the ledger itself. List recent stock adjustments (Swell's `GET /products:stock` supports filters and sorting) and look for adjustments without a reason or without a source reference. Those are manual edits outside your process, and they are the first place to look when numbers drift.
Step 10: Roll Out in Stages
Turning on bidirectional sync for every channel at once is how merchants end up with a weekend of cancellations. Go in this order.
- Audit SKUs and identifiers. Export every sellable SKU from every channel. Find duplicates, missing SKUs, bundles, and variants that exist on one channel but not another. Every channel listing must map to exactly one SKU (and variant) in your platform.
- Build the identifier map. Store channel listing IDs, ASINs, eBay offer IDs, TikTok SKU IDs, POS item IDs, and WMS item codes against your platform SKU. Sync matches on this map, never on titles.
- Set the ownership map and formula. Document which system owns on-hand per location, which events commit stock per channel, and the buffer per channel.
- Run one channel read-only. Pick the channel with the cleanest data. Compute what you would publish and compare it with what the channel shows today, without writing anything. Fix mapping errors until the diff is explainable.
- Turn on writes for that channel. Start with zero-quantity pushes and decreases only for a few days, then enable increases.
- Add buffers. Start conservative, then lower them as your oversell log shows the sync is reliable.
- Add reconciliation. Run the hourly and nightly jobs in report-only mode for a week, then let them write corrections.
- Expand to the next channel. Repeat steps 4 to 7. Bring marketplaces with strict cancellation penalties online after the web store and WMS integration are stable.
- Retire the old paths. Turn off any legacy connector or spreadsheet process that still writes quantities.
Reality Check: Costs and Operational Gotchas
- Bundles and kits. A bundle's available quantity is the minimum available across its components. If channels sell components and bundles separately, the bundle must be computed, not stocked.
- Manual edits. Staff editing quantities in a marketplace seller portal create a second writer. Lock that down or have your hourly job overwrite it.
- Rate-limit budgets can be shared. Amazon applies some listings limits per application as well as per account-application pair, so know which tools draw from the same bucket.
- Platform cost scales with revenue. Middleware and connectors often price per order or per channel. On the commerce side, Swell publishes plans with revenue ceilings and overage rates that fall as you upgrade: Starter is $29 per month, Basic $79, Standard $299, and Unlimited $2,250 (billed yearly), with trailing-12-month revenue ceilings of $50K, $250K, $1M, and $5M and overage beyond the ceiling of 2%, 1.5%, 1%, and 0.4% respectively, with custom pricing above $10M a year. Check the pricing page for current terms before modeling costs.
- Marketplace metrics lag. Cancellation and defect rates are measured over rolling windows, so a bad sync weekend can affect your account standing well after the bug is fixed.
Decision Guidance
Use these rules to decide how much of the above you need.
- One web store and one 3PL: the ownership map, delta adjustments from the 3PL, and a nightly reconciliation are enough. Skip per-channel buffers.
- Web plus POS: add store locations, keep store stock off online channels unless you ship from stores, and handle offline uploads as idempotent deltas.
- Web plus one or more marketplaces: this is where buffers, zero-first pushes, and the oversell playbook pay off. Measure sync latency per marketplace before setting buffers.
- B2B alongside retail: make quote holds explicit with expiry dates, and decide whether wholesale allocations are invisible to marketplaces.
FAQ
What is the difference between on-hand, committed, and available stock?
On-hand is the physical count in a location. Committed is stock promised to orders that have not shipped. Available is on-hand minus committed, and the quantity you publish to a channel is available minus that channel's buffer.
Should I use webhooks or polling to sync inventory?
Both. Webhooks deliver changes within seconds, while scheduled polling catches anything a webhook missed during an outage or after retries ran out. In Swell, you can receive webhooks and also list events through `GET /events` as a backstop.
How do I stop overselling on Amazon and eBay?
Commit stock as soon as marketplace orders are imported, publish quantities with a buffer sized to your measured sync latency, push zero-quantity updates first, and publish zero when stock falls below a threshold. Overselling there is expensive: Amazon advises a cancellation rate below 2.5%, and eBay counts out-of-stock cancellations as transaction defects.
Should inventory updates be absolute quantities or adjustments?
Use adjustments (deltas) into your system of record. Use absolute quantities only for scheduled reconciliation against a trusted count, and for outbound pushes to channels whose APIs require them.
Which system should be the source of truth for inventory?
No single system owns everything. The WMS or 3PL owns on-hand, the commerce platform owns committed and available-to-sell, and channels only display a derived quantity. Problems start when two systems write the same number.
How often should I run inventory reconciliation?
A common setup is an hourly comparison of published versus computed channel quantities and a nightly comparison of WMS on-hand against commerce, writing corrections as reason-coded adjustments and alerting when drift crosses a threshold.
Build your store on Swell
Headless commerce with a full API on every plan, native subscriptions and custom data models. Start a store and see how it fits your build.