How a restock gets detected
The system doesn’t poll the shop or guess; it waits for the store’s own inventory to say a SKU is back. This post is about that trigger: how a raw stock-level webhook becomes a confident “this item genuinely restocked, by this many units, once” — and why telling a real restock from a stock level wobbling around the threshold is the whole job.
Key takeaways
- The trigger is a stock-level webhook from the store’s inventory system, posted to one Lambda Function URL — no polling, no API Gateway.
- The webhook is verified by its signature before anything runs, so nobody can spoof a restock and make the system message the list.
- A restock is the crossing from below the threshold to above it — not every stock message, so a level wobbling near the line fires nothing.
- Each genuine restock gets a stable id and records the quantity available, which is what caps the batch so the system never oversells.
- One notify batch is enqueued per restock, idempotently, so provider retries or duplicate events can’t notify the queue twice.
Waiting for the shop to say it’s back
The system never guesses whether an item has returned, and it never polls the shop asking. It waits for the store’s own inventory to tell it. Most e-commerce platforms emit an event when a SKU’s stock level changes — an inventory-level or product-update webhook carrying the SKU, the new quantity, and a timestamp. That POST lands on a single Lambda Function URL, separate from the capture endpoint in Part 2 because it’s a different trust relationship: this one comes from the shop platform, signed, and it’s the trigger for every notice the system will ever send.
The detect function’s job is deceptively narrow. It is not “stock changed, so message everyone”. Stock levels change constantly — a sale drops the count, a return bumps it, a stocktake corrects it, the platform re-sends an event it thinks failed. The one moment worth acting on is the specific one where an item that was genuinely out (or below the threshold) comes back above it. Telling that single crossing apart from all the ordinary noise is the whole job of this step, and getting it wrong in either direction is expensive: miss the crossing and the queue never hears; over-fire and you spam people for a stock level twitching around zero.
Prove it’s real, then prove it’s a crossing
The first check is authenticity. The Function URL is public, so the function verifies the platform’s signature — a hash of the request body signed with a shared secret held in Secrets Manager — before it trusts a single field. A bad signature is dropped with a 401 and nothing else happens. Without this, anyone who found the URL could post a fake “the walnut chair is back, quantity 500” and make the system message the whole list; with it, only the shop’s platform can trigger a restock.
The second check is the one that matters most: is this a genuine restock, or just noise? The function reads the SKU’s last known level from the catalogue table, compares it to the new quantity, and applies the threshold from the settings doc (often simply “was at or below zero, now at or above one”, but a shop can set a higher bar — don’t bother the list until at least three are in). Only a transition from below the threshold to above it counts as a restock. A count that was already above the line and just went higher isn’t a restock — those people were told last time. A level that dips to zero and bounces back to zero fires nothing. To stop a flapping SKU (one that oscillates around the threshold as concurrent orders and restocks land) from firing repeatedly, the function also applies a short cool-down: once it has fired for a SKU, it won’t fire again for that SKU until the level has clearly settled and a sensible interval has passed.
Counting the units, and firing once
A confirmed crossing does two more things before it hands off. First, it records how many units came in — the quantity from the event, clamped to what the catalogue now says is genuinely sellable. This number is the whole reason the system can promise never to oversell: it is the cap the notifier in Part 4 obeys, the maximum number of people who will be invited for this restock. Four chairs in means at most four notices go out; the fifth person waits. Second, it mints a stable restock id for this event and writes it with a conditional write to the restocks table, so the very act of recording the restock is what makes firing idempotent. If the platform re-sends the same event, or two near-simultaneous webhooks describe the same crossing, the conditional write fails on the second one and no second batch is enqueued.
Only once a restock is verified, confirmed as a real crossing, de-duplicated, and counted does the function enqueue exactly one notify-batch job on SQS — carrying the SKU, the restock id, and the unit count — for Part 4 to work through. Everything slow (reading the whole waiting list, calling the model, sending messages) happens off that queue, not in the webhook, so the store’s platform gets a fast acknowledgement and never waits on the shop’s behalf. The odd case here — a restock with an implausible quantity, or a SKU the catalogue doesn’t know — isn’t acted on blindly; it’s recorded and escalated to a person, because messaging a queue for a mistake is far worse than a moment’s delay while someone checks.
Design rules that shaped the detect step
- Wait to be told. The store’s inventory webhook is the trigger; the system never polls and never guesses a return.
- Verify before you trust. A bad signature is dropped with a 401 — the URL being public is fine because the secret isn’t.
- A restock is a crossing, not a change. Only a genuine move from below the threshold to above it fires; ordinary stock noise doesn’t.
- Count the units up front. The quantity available is recorded now, because it’s the cap that stops the notifier ever overselling.
- Fire exactly once. A minted restock id written conditionally makes the batch idempotent — retries and duplicates enqueue nothing.
- When it looks wrong, a person. An implausible quantity or an unknown SKU escalates rather than messaging the queue on a mistake.