Developers

Inventory Webhooks: Real-Time Stock Sync Without the Polling

7 min readBy Inventoros Team
Inventory Webhooks: Real-Time Stock Sync Without the Polling

Inventory webhooks are how your inventory system tells the rest of your stack that something changed, the moment it changes, instead of you asking over and over. A unit sells, a purchase order is received, stock drops below a threshold, and your storefront, accounting, and shipping tools all find out in the same second.

If you are syncing stock across more than one system, this is the difference between "our numbers are always right" and "we oversold again." Here is how inventory webhooks work, how to verify them, and how to handle the parts that bite people in production.

What an inventory webhook actually is

A webhook is an HTTP POST that your inventory system sends to a URL you own. You register an endpoint, subscribe to the events you care about, and from then on the inventory system pushes a small JSON payload to that URL whenever one of those events fires.

That is the whole idea. You are not calling an API. The API is calling you. Your job is to stand up an endpoint that accepts the POST, verifies it is genuine, and does something useful with the payload.

Webhooks vs polling

The alternative to webhooks is polling: hitting GET /products on a timer and diffing the results to spot changes. It works, and it is also wasteful and slow. Here is the honest comparison.

Polling Webhooks
Freshness Stale up to your poll interval Near-instant
Requests Thousands of "did anything change?" calls One call per real event
Cost You pay for empty responses You pay for signal only
Complexity Simple to start, scales badly Needs a public endpoint and verification
Missed changes Fine if it lags Needs retry handling

Polling a busy catalog every 30 seconds means roughly 2,880 requests a day per endpoint, and the vast majority return "nothing changed." Webhooks flip that: a product with 40 stock movements a day sends 40 calls. You do more work up front to stand up the receiver, and you stop hammering an API for data that has not moved.

Events worth subscribing to

Do not subscribe to everything. A firehose is as useless as no data. Pick the events that map to an action on your side:

  • stock.updated: a stock level changed at a location. The core event for sync.
  • stock.low: a product crossed its reorder point. Trigger a purchase order or an alert.
  • order.received: inbound stock landed. Update what is available to sell.
  • product.updated: name, SKU, price, or variant changed. Push to your storefront.
  • product.deleted: pull it from every channel before you sell a ghost.

A good REST and GraphQL API lets you subscribe per event and per location, so a warehouse-only tool never hears about the retail floor.

A worked example: verifying a signed webhook

Never trust a webhook because it hit your URL. Anyone who learns the URL can POST to it. The fix is a signature. The inventory system signs each payload with a shared secret, sends the signature in a header, and you recompute it to confirm the call is real and untampered.

A typical payload looks like this:

{
  "event": "stock.low",
  "id": "evt_9f2a7c",
  "created_at": "2026-07-04T14:03:11Z",
  "data": {
    "product_id": 412,
    "sku": "CASE-IP15-BLK",
    "location": "warehouse-1",
    "on_hand": 240,
    "reorder_point": 250
  }
}

It arrives with a header like X-Inventoros-Signature: sha256=.... To verify it in Node:

import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Two rules that matter here. First, hash the raw request body, not a re-serialized object, because reordered keys or different whitespace change the hash. Second, use a constant-time compare (timingSafeEqual), not ===, so you do not leak the secret through timing. If verification fails, return 401 and stop. Do not process the payload.

How to set up inventory webhooks

The flow is the same across most systems:

  1. Stand up an endpoint. A single POST route, publicly reachable over HTTPS. It reads the body, verifies the signature, and returns fast.
  2. Register it. Point the inventory system at the URL and store the signing secret somewhere safe, not in your repo.
  3. Subscribe to events. Pick the handful you will actually act on.
  4. Verify, then queue. Confirm the signature, push the job onto a queue, and return 200 immediately. Do the slow work (calling other APIs, writing to channels) in a background worker.
  5. Test with a replay. Fire a test event and watch it flow end to end before you rely on it.

Returning 200 fast is not a nicety. Most senders treat a slow or failed response as a delivery failure and retry, which brings us to the part everyone underestimates.

Retries and idempotency

Networks blink. Your app deploys. A webhook will occasionally fail to reach you, so any sender worth using retries with backoff. That guarantees delivery, and it also guarantees you will sometimes get the same event twice.

If receiving order.received twice adds the stock twice, your counts are now wrong and you did it to yourself. The fix is idempotency: treat each event id as a key you process exactly once.

if (await seen(event.id)) return res.sendStatus(200);
await process(event);
await markSeen(event.id);

Store processed ids for a rolling window (a day is plenty for most retry schedules) and short-circuit duplicates with a 200 so the sender stops retrying. Design every handler to be safe to run twice. That single habit removes a whole category of "the numbers drifted and nobody knows why" bugs.

Where Inventoros fits

Inventoros ships signed, retrying inventory webhooks in the open-source core, alongside a full REST and GraphQL API and an MCP server for AI assistants. Because you self-host it, the webhook endpoints live on your own infrastructure next to the systems they sync with, and there is no per-call pricing to plan around. The documentation walks through registering endpoints, event types, and signature verification.

FAQ

What is the difference between an inventory webhook and an inventory API? The API is something you call to read or change data. A webhook is the inventory system calling you when data changes. You use the API for on-demand reads and writes, and webhooks for real-time notifications so you are not polling the API to spot changes.

How do I secure an inventory webhook endpoint? Serve it over HTTPS, verify the signature on every request using the shared secret and a constant-time compare, and reject anything that fails. Optionally restrict inbound IPs and rate-limit the route. Never act on an unsigned or unverified payload.

What happens if my endpoint is down when an event fires? A good sender retries with exponential backoff, so a short outage means delivery is delayed, not lost. This is exactly why your handlers must be idempotent: retries can deliver the same event more than once, and processing it twice should never corrupt your stock counts.

Do I need webhooks if I already poll the API? If real-time accuracy matters, yes. Polling is stale between intervals and burns requests on empty responses. Webhooks push only real changes the instant they happen, which is what keeps stock levels correct across multiple sales channels.