DEV Community

Nventory
Nventory

Posted on

How to Build a Multichannel Order Manager Event Pipeline That Actually Scales

Most multichannel order backends fail the same way: every channel gets its own polling job, its own data model, and its own sync schedule. It works at 10 orders a day. At 500 it produces race conditions, inventory drift, and duplicate fulfillments nobody notices until a customer complains.

The fix is an event-driven pipeline with a unified data model. Here's the architecture that scales.

The core problem with channel-per-silo architecture

When each channel has its own sync job, inventory state is never globally consistent. Channel A sold a unit at 11:52. Channel B's sync runs at 12:00. In that 8-minute window, Channel B can sell the same unit. Now you have two confirmed orders for one item.

The race condition isn't a bug you can patch — it's structural. The only fix is a single inventory state that all channels read from and write to atomically.

Event-driven pipeline architecture

javascript
// Unified order event schema — normalize all channels to this
const OrderEvent = {
eventId: 'uuid', // idempotency key
channel: 'shopify|amazon|ebay|tiktok|walmart',
externalOrderId: 'string',
placedAt: 'ISO8601',
lineItems: [
{
sku: 'string',
quantity: 'number',
warehouseId: 'string|null' // null = needs routing
}
],
customer: {},
fulfillmentRequired: 'boolean'
};

Every channel webhook maps its native payload to this schema before anything else touches it. Downstream systems only ever see OrderEvent never raw channel payloads.

Webhook handlers per channel

javascript
// Shopify webhook handler
app.post('/webhooks/shopify/orders/create', async (req, res) => {
verifyShopifySignature(req); // always verify first

const event = normalizeShopifyOrder(req.body);
await eventQueue.publish('order.created', event);

res.status(200).send('OK'); // respond fast, process async
});

// Amazon webhook handler — same pipeline, different normalizer
app.post('/webhooks/amazon/orders/create', async (req, res) => {
verifyAmazonSignature(req);

const event = normalizeAmazonOrder(req.body);
await eventQueue.publish('order.created', event);

res.status(200).send('OK');
});

Key rule: respond to the webhook immediately, process asynchronously. Channels retry if you don't respond within a few seconds — slow processing causes duplicate events.

Inventory decrement with optimistic locking

javascript
async function decrementInventory(sku, quantity, eventId) {
// Idempotency — don't decrement twice for the same event
const processed = await db.processedEvents.findOne({ eventId });
if (processed) return;

const result = await db.inventory.findOneAndUpdate(
{ sku, quantity: { $gte: quantity } }, // only if enough stock
{ $inc: { quantity: -quantity } },
{ returnDocument: 'after' }
);

if (!result) {
await eventQueue.publish('order.insufficient_stock', { sku, quantity, eventId });
return;
}

// Propagate new stock level to all channels immediately
await propagateStockUpdate(sku, result.quantity);
await db.processedEvents.create({ eventId, processedAt: new Date() });
}

The $gte check and atomic update together prevent overselling at the database level — no amount of concurrent orders can decrement below zero.

Stock propagation back to channels

javascript
async function propagateStockUpdate(sku, newQuantity) {
const channels = await db.connectedChannels.find({ sku });

await Promise.allSettled(
channels.map(channel =>
updateChannelStock(channel, sku, newQuantity)
.catch(err => retryQueue.add({ channel, sku, newQuantity }, { attempts: 5 }))
)
);
}

Promise.allSettled ensures one channel failing doesn't block others. Failed updates go into a retry queue with exponential backoff never dropped.

The result

This pipeline is what a proper multichannel order manager implements under the hood unified event schema, atomic inventory decrements, idempotent processing, and parallel channel propagation with retry safety.

If you'd rather not build this yourself, Nventory ships this architecture out of the box across 30+ channels - webhook-driven, idempotent, retry-safe, free plan available.

Top comments (0)