TL;DR
- Treat stock as an event-sourced ledger, not a mutable counter.
- Every reservation must be idempotent — assume every webhook will arrive twice.
- The 3 races: cross-marketplace double-buy, returns vs new orders, and stale-snapshot updates.
- Buffer your published "available" count by 5% or 5 units (whichever is higher) to absorb sync latency.
The problem isn't sync, it's consistency
Multichannel sellers describe their problem as "keep stock in sync across Amazon, Allegro, eBay and our website." It sounds like a simple data-replication problem. It isn't. It's a distributed-systems problem with non-cooperative parties (the marketplaces), variable webhook latency, and zero atomicity guarantees across systems.
Why we use an event-sourced ledger
The naive approach is a counter: SKU-1234 has 47 units. Every order decrements; every restock increments. This breaks the moment you have concurrent writes from two marketplaces and a return.
The model we use: an append-only ledger of stock events.
- RESTOCK +N (warehouse intake)
- RESERVE -N (order placed)
- RELEASE +N (order canceled before fulfillment)
- FULFILL 0 (reservation realized — moves stock from reserved to shipped)
- RETURN +N (post-shipment return)
- ADJUST ±N (cycle count correction, with a reason)
Available stock is a derived projection: sum(RESTOCK + RETURN + RELEASE + ADJUST) - sum(RESERVE). The published "available" pushed to marketplaces is the projection minus a safety buffer.
Why this works
- Concurrent writes are serializable on the ledger; the projection is eventually consistent but always derivable.
- Audits are trivial — the ledger explains every unit.
- Replays let you reconstruct stock at any past point, which is gold when reconciling marketplace settlements.
Idempotent reservations
Every marketplace will send you the same order webhook two or three times. If your handler is not idempotent, you'll reserve stock twice for the same order. The fix:
- Use the marketplace's order ID as a natural idempotency key.
- Make every reservation a single insert with
ON CONFLICT (order_id) DO NOTHING. - Never trust your own retries either — your job runner will re-deliver too.
The 3 race conditions
Race 1: cross-marketplace double-buy
Two buyers on two marketplaces buy the last unit within 30ms of each other. Both webhooks arrive before either marketplace gets your "0 available" update.
Mitigation: publish "available" with a buffer; treat the second reservation as a known failure and refund/cancel automatically with a templated apology that mentions cross-platform sync. Aim for < 0.3% of orders affected.
Race 2: returns vs new orders
A return is processed at the warehouse at 14:02. A new order arrives at 14:03 and reserves against the stock that the return added. The return webhook to the marketplace fires at 14:05. The marketplace believes you have 0 stock available between 14:02 and 14:05 and stops showing your listing.
Mitigation: emit the RETURN event before warehouse confirmation if your provider is reliable, or accept the visibility gap.
Race 3: stale-snapshot updates
Your batch sync job pulls inventory at 09:00 and pushes it to Amazon at 09:04. Between those four minutes, three orders came in. You just told Amazon you have 47 when you have 44.
Mitigation: never push absolute stock from a snapshot. Push deltas based on the ledger, or push absolutes computed at push-time.
Operator's tip: If you're at less than 1,000 orders/day, you can usually get away with a 5-unit buffer and 60-second sync interval. Above that, you need to invest in real-time event-driven architecture or you'll be firefighting weekly.
Sizing the buffer
The buffer is your hedge against sync latency. Set it to max(5 units, 5% of stock, expected sales during 2× P99 sync latency). Most sellers we work with land at 5–8% effective buffer.
What to monitor
- Reservation conflict rate — should be under 0.3%.
- P99 sync latency — should be under 90 seconds.
- Stranded reservations — orders reserved but not fulfilled within 48h.
- Ledger drift — projected vs warehouse cycle counts. A drift over 1% means a reconciliation bug.
Want this architecture handled for you? FeedPilot's inventory engine runs exactly this design and is battle-tested at 12M+ events per day. Talk to our team.
Stop overselling. Start scaling.
FeedPilot syncs inventory across every marketplace in under 60 seconds.
Try for free →