DEV Community

Pradip Parmar
Pradip Parmar

Posted on

Preventing Overselling: Building Real-Time Inventory Sync Between POS and E-Commerce

Imagine a customer walks into your retail store and purchases the last unit of a product. At the same time, another customer orders that exact product from your website.

Your physical store shows zero stock, but your e-commerce website still shows one unit available.

The online order gets confirmed, and now you have two customers expecting the same product.

This is overselling, and it is one of the most common inventory management challenges for businesses selling through both online and offline channels.

The problem is not necessarily inaccurate inventory data. It is often a synchronization problem: two systems update or read stock without a reliable way to coordinate those changes.

Let's explore how to build a real-time inventory synchronization system that keeps your POS and e-commerce platform consistent.

Why Inventory Synchronization Is Difficult

A business might use a POS system for physical store sales, an e-commerce platform for online orders, and an inventory service to manage product availability.

Each system can generate inventory events independently.

For example:

  • A cashier sells a product at the physical store.
  • A customer places an order online.
  • An online order is cancelled.
  • A returned product is added back to stock.
  • A warehouse transfers inventory to another outlet.

If these changes are not synchronized correctly, each sales channel may display a different stock quantity.

A basic implementation might update inventory through an API call after every transaction. However, network delays, failed requests, concurrent purchases, and duplicate events can still cause inconsistencies.

The key principle is that inventory must be updated safely before a sales channel promises the product to a customer.

1. Build a Central Inventory Service

Instead of allowing the POS and e-commerce systems to maintain independent stock quantities, introduce a central inventory service.

Both systems read from and submit inventory changes to this service.
A simplified architecture looks like this:

     Physical POS
           |
           v
    Inventory API
           |
           v
   Inventory Database
           ^
           |
      Inventory API
           ^
           |
    E-commerce Store

           |
           v
     Event Queue
           |
    +------+------+
    |             |
Enter fullscreen mode Exit fullscreen mode

POS Updates Storefront Updates

The inventory database becomes the authoritative source for stock quantities. The POS and storefront can maintain local or cached copies for display, but they should not independently decide whether the final unit can be sold.

For a small system, a well-designed backend and relational database may be sufficient. A larger system can introduce a message broker, such as Kafka or RabbitMQ, when asynchronous event processing becomes necessary.

The important part is not the number of services. It is having a reliable process for accepting, validating, and recording stock changes.

2. Use Atomic Database Updates

One of the most important protections against overselling is an atomic inventory update.

Suppose a product has only one unit remaining. Two customers attempt to purchase it at almost the same time.

A dangerous implementation looks like this:
const product = await getProduct(productId);

if (product.stock > 0) {
await updateStock(productId, product.stock - 1);
}

Both requests might read a stock quantity of 1. Both pass the condition, and both attempt to sell the same unit.

A safer approach is to perform the stock check and decrement in a single database operation.

For example, using PostgreSQL:
UPDATE inventory
SET available_quantity = available_quantity - 1
WHERE product_id = $1
AND location_id = $2
AND available_quantity > 0
RETURNING available_quantity;

If the query returns a row, the stock decrement succeeded. If it returns no rows, the product was unavailable at that location.

The database evaluates the update atomically, preventing concurrent requests from both decrementing the same final unit.

For multi-item orders, however, you need additional safeguards. If several products must be reserved together, use a database transaction and roll back the entire reservation if any item is unavailable.

3. Separate Physical Stock From Reserved Stock

A common mistake is treating every unit in the warehouse as immediately available for sale.

Consider a product with the following quantities:

  • Physical stock: 20 units
  • Units reserved for online orders: 4
  • Units available for sale: 16

The basic calculation is:

Available Stock = Physical Stock - Reserved Stock

A more advanced system may also account for damaged goods, safety stock, quality-control holds, and stock allocated to specific channels.

When an online customer places an order, the system should reserve the required units before confirming that inventory is available.

A typical order lifecycle looks like this:

  1. The customer adds products to the cart.
  2. The checkout service requests an inventory reservation.
  3. The inventory service verifies availability and reserves the units.
  4. The payment process completes.
  5. The reservation is converted into a confirmed sale.
  6. If payment fails or the reservation expires, the units are released.

Cart contents should not normally reserve stock indefinitely. Otherwise, abandoned carts can make products unavailable to genuine buyers.

Reservation expiry, payment retries, and cancellation handling should be explicit parts of the design.

4. Synchronize Changes Through Events

Updating the inventory database does not automatically update every connected sales channel.

A POS system might need the new quantity immediately, while an e-commerce storefront may receive updates through a WebSocket connection, server-sent events, or a message-driven update process.

An inventory event could look like this:
{
"event_id": "evt_12345",
"event_type": "inventory.updated",
"product_id": "SKU-1001",
"location_id": "STORE-01",
"available_quantity": 7,
"version": 42
}

After a sale, the inventory service commits the stock change and publishes an event. Subscribers then update their inventory views or caches.

Possible technologies include:

  • Redis Pub/Sub: useful for lightweight real-time notifications, but messages can be lost when subscribers are disconnected.
  • RabbitMQ: useful for durable message delivery and work queues when configured appropriately.
  • Apache Kafka: useful for high-volume event streams and replayable event histories.
  • WebSockets: useful for pushing inventory changes to connected dashboards and applications.

These tools solve different problems. WebSockets deliver updates to clients; a durable message broker helps services process events reliably. Neither should replace database-level stock protection.

Avoid the Dual-Write Problem

There is another subtle failure case.

The inventory database successfully records a sale, but the application crashes before publishing the corresponding event. The stock is correct in the database, but connected systems never receive the update.

The transactional outbox pattern helps solve this problem.

Within one database transaction, write both the inventory change and an event record to an outbox table. A background worker publishes pending outbox records to the message broker and marks them as processed.

If the worker crashes, it can retry publication.

Because retries can result in duplicate messages, consumers should also be idempotent: processing the same event more than once must not apply the stock change repeatedly.

5. Make Inventory Events Idempotent

Distributed systems cannot always guarantee that a message is delivered exactly once to every consumer.

A broker or worker may retry a message after a timeout, even if the original attempt succeeded.

If every delivery reduces stock again, inventory becomes incorrect.
One solution is to assign every event a unique ID and record which events have already been processed.

For example:
CREATE TABLE processed_inventory_events (
event_id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ DEFAULT NOW()
);

Within a transaction, the consumer attempts to insert the event ID. If the ID already exists, the event has been processed and should not apply the same business operation again.

The deduplication record and corresponding inventory effect must be handled transactionally. Otherwise, a crash between the two operations can still create inconsistencies.

Also, prefer events that represent a specific operation or inventory version over blindly applying repeated absolute updates.

6. Handle Multiple Stores and Warehouses

For businesses operating multiple outlets, a single product may have different stock levels at different locations.

For example:

Location           Physical Stock  Reserved   Available
Surat Store        12              3          9
Ahmedabad Store    8               2          6
Central Warehouse  50              10         40
Enter fullscreen mode Exit fullscreen mode

A product's global stock quantity is not enough to determine whether a particular outlet can fulfil an order.

Your inventory model should identify stock by product and location, with additional fields for reservations and stock status where needed.

You must also define fulfilment rules. Should an online order use the nearest outlet, the central warehouse, or whichever location can fulfil it fastest?

If multiple channels share the same physical inventory, stock allocation rules can help prevent one channel from consuming every unit needed by another.

For high-demand products, businesses can also maintain a safety buffer or allocate limited quantities to specific channels.

7. Monitor Synchronization Delays and Failed Events

Real-time synchronization does not mean every update will arrive instantaneously.

A database may commit a stock change immediately while a downstream storefront takes several hundred milliseconds-or longer-to receive the event.

Monitor the complete process instead of measuring only API response time.

Useful metrics include:

  • Inventory update propagation latency.
  • Number of failed or repeatedly retried events.
  • Age of the oldest pending outbox record.
  • Number of rejected reservations due to insufficient stock.
  • Differences between authoritative inventory and channel-level projections.
  • Frequency of overselling incidents.

Set practical service-level objectives for synchronization latency and alert when they are breached.

You should also implement reconciliation jobs that periodically compare derived inventory views with the authoritative database. Reconciliation is a safety net, not a replacement for atomic inventory updates.

8. Test the System Under Concurrent Purchases

A synchronization mechanism may work perfectly during normal usage and fail during a flash sale.

Create automated tests for scenarios such as:

  • Two customers purchasing the final unit simultaneously.
  • A physical sale occurring during online checkout.
  • Payment succeeding while a downstream notification fails.
  • The same event being delivered more than once.
  • A reservation expiring while payment is being processed.
  • A store losing its network connection and reconnecting later.

For example, if 100 concurrent requests attempt to buy a product with only 10 available units, the system should accept no more than 10 reservations, assuming no other inventory changes occur during the test.

Use load testing to measure contention, database performance, event processing delays, and recovery behavior.

Final Thoughts

Preventing overselling is not simply a matter of refreshing product quantities more frequently. It requires a consistent inventory model, atomic stock updates, reservation management, reliable event delivery, and well-defined recovery mechanisms.

For many retail businesses, the most practical starting point is a central inventory service backed by PostgreSQL, atomic stock operations, and a transactional outbox. Real-time notifications can then keep POS dashboards and e-commerce storefronts updated without making them responsible for the final stock decision.

As the business grows, you can introduce message brokers, more sophisticated fulfilment rules, and location-specific inventory allocation.

The goal is straightforward: whether a customer buys from a physical store or an online storefront, the system should make one reliable decision about whether that product can actually be sold.

How does your current POS and e-commerce setup handle concurrent orders?Are you using a central inventory service, webhooks, or a message broker? Share your approach and the challenges you've encountered in the comments.

Top comments (0)