Blog Details

blog
about

Denial of Inventory: The Bot Attack That Looks Like Demand

Denial of Inventory: The Bot Attack That Looks Like Demand

At 00:00:14 on a Black Friday, a storefront sold out of a limited-run product. Four minutes later the same units were listed on a resale marketplace at three times list price. Almost nobody bought anything from the retailer. The units had been parked in carts by a few thousand sessions that never reached payment.

The inventory never left the warehouse, but the dashboard logged it as demand.

That is denial of inventory. Airlines call their version seat spinning. Retail calls it cart hoarding. Ticket sellers call it queue inflation when bots flood a waiting room so that real buyers fall past the sale window. The mechanics differ and the economics do not: every unpaid hold removes a unit from the sellable pool, and every wasted minute pushes a customer toward a competitor. A launch that sells out in ninety seconds looks like a triumph in the analytics and reads like a loss in the returns and repeat-purchase numbers six weeks later.

Why the WAF sees nothing

A hoarding bot is not a malformed request. It is the opposite of one. It registers an account, loads the product page, adds the item, calls the inventory endpoint, and stops short of payment. Each of those calls has a legitimate shape, a valid session cookie, and a user agent string that resolves to a browser version that shipped this year. Volumetric defenses count requests, so traffic that behaves perfectly scores clean.

Imperva's 2026 Bad Bot Report puts the scale in context. Automated traffic was more than 53% of all web traffic in 2025, up from 51% a year earlier, and bad bots accounted for 40%. The report attributes 21% of bot attacks to business logic abuse rather than scraping, and retail leads the targeted sectors at 24%. Cloudflare measured bots at 57.4% of HTML requests in the seven days to 5 June 2026. This is not an edge case you filter out at the perimeter. It is most of the traffic that arrives.

The AI line in that research matters more than the headline. DataDome measured AI-driven traffic growing 82% in a year, and Imperva counted a 12.5-fold rise in mitigated AI-driven attacks, taking blocked incidents from roughly 2 million a day to 25 million. The tooling got cheaper, and the sessions got harder to separate from people. Operators also learned to stay inside the rules you wrote.

Four places the money leaks

Most teams guard one of these and assume the others are covered.

Catalogue and inventory APIs

If the inventory endpoint answers a bare GET with no session binding, a script can poll availability across the whole catalogue and buy exactly the SKUs it wants the moment stock returns. Imperva documented a scalping operation that ran this way: the bots found a publicly exposed API and bypassed the storefront entirely. The front-end bot challenge never fired because the requests never reached the front end. Partner feeds, mobile clients, and internal dashboards often hit the same handlers with looser checks than the web path.

Cart and reservation holds

A hold that expires in thirty minutes with no per-account cap is a free option on your stock. One email address, one card fingerprint, or one device will usually open twenty of them. The limit that matters is not how many items a cart can contain, it is how many separate carts one identity can own at once, and that is the number almost nobody caps.

The waiting room

A queue makes a single trust decision at the door. If the queue token is not re-verified on inventory reads and on checkout submission, a bot joins once, captures a token, and replays it for as long as the drop runs. Queue inflation is a deliberate tactic rather than a side effect: thousands of fake sessions raise the wait for everyone else and drain slots before real buyers arrive. Once the queue is weaponized, honest customers get the worst of both outcomes, a long wait and an empty shelf.

Checkout submission

Retry logic and duplicate submissions consume the same counters as real purchases. If a reservation is released on payment failure but not on plain abandonment, a bot can release and re-hold in a loop and keep a unit out of circulation indefinitely. The release path is where reservation systems usually break, because developers test the happy flow and the decline flow and skip the walk-away.

Controls that move the number

None of this is exotic. It is ordinary engineering with a threat model attached to it.

Bind holds to an identity and cap them

Attach an owner to each reservation, using account id, hashed card fingerprint, device signal, and IP as a weak tiebreaker, then cap it: one unit per SKU per identity during a drop, three open holds in total. Make expiry absolute and short. Five to ten minutes suits high-demand items, and any extension should require a logged-in account. Then check that the expiry is actually enforced. A soft expiry that runs on a cron every fifteen minutes is a fifteen-minute exploit window, and you will find out about it on the worst possible day.

Verify the queue token on every protected call

Sign tokens, bind them to a visitor key, check them at the inventory read, at add-to-cart, and at checkout submit. Reject anything that arrives without one. For the highest-risk actions, make them single use. If your queue vendor supports uniqueness on visitor keys, turn it on, so one identity gets one place in line rather than forty.

Rate limit by object, not by endpoint

Ten requests per second per IP against the inventory API tells you nothing useful. Ten distinct SKUs read per minute by a session that has never converted is a scraper. Limit on distinct object cardinality, on hold-to-purchase conversion per identity, and on account creation velocity from a shared payment or device signal. Endpoint counters reward patience, and hoarding bots are patient by design.

Score at the API layer

The mobile app, the storefront's client code, and every partner feed hit the same handlers. Bot management that sits in front of HTML pages protects only the part of the surface humans use. Put the scoring call in the gateway or middleware so the decision applies to every caller, including the ones that skip your JavaScript and never see your challenge.

Alert on demand that does not convert

This is the cheapest control available and the one most teams skip. Track reservation-to-purchase ratio per SKU per hour, the distribution of hold durations, and sessions with heavy catalogue activity and zero funnel progression. A drop where 4,000 units are held and 60 are sold is not a traffic spike. You want that alert at 200 holds, not in the postmortem.

Reading a campaign in your own logs

Before buying tooling, look at what your access logs already contain. Hoarding sessions cluster in ways real customers do not:

  • Session age at first hold is minutes, not days, and the account was created in the same window.
  • Time from add-to-cart to abandonment is tightly grouped, because the script sleeps on a fixed interval.
  • Requests concentrate on a handful of SKUs while other traffic spreads across the catalogue.
  • Headers look consistent per cluster and inconsistent with the claimed device, especially HTTP/2 settings and header order.
  • A small set of payment fingerprints or shipping address shapes spreads across a large number of accounts.

One pattern is worth checking on your own data this week: take a sample of sessions that opened a hold and never paid, then look at what they loaded before the hold. Hoarding traffic often skips the homepage, the category page, and the cart page entirely, going straight from a search API call to the add-to-cart endpoint. That navigation shape alone is enough to flag the cluster, and it costs nothing to compute from logs you already keep.

Do not block your new legitimate traffic

Shopping agents from Google, OpenAI, and a growing list of smaller crawlers read product feeds and sometimes complete purchases on a user's behalf. They are automated, they are authorized, and blanket bot blocking quietly removes you from AI shopping surfaces that are becoming a real acquisition channel. Classify before you block. Verify crawler identity through published IP ranges and reverse DNS, accept signed agent requests where the platform supports them, and keep a documented allowlist with a named owner and a review date.

The line to draw is authorization, and automation has little to do with it. A script holding 200 carts is unauthorized. A crawler reading your feed hourly under a verified identity is distribution. The same discipline applies to your own automation: if a pricing bot or an internal assistant talks to the commerce API, give it its own credentials and its own rate budget so it can be told apart from everything else.

A quarter of work, in order

  1. Instrument the funnel by SKU and identity. Publish the reservation-to-purchase baseline. Without that number you cannot argue for the budget, and you cannot prove the fix worked.
  2. Cap holds per identity and shorten expiry. Test the release path with a scripted abandon, not with a declined card.
  3. Move bot scoring to the API layer. Confirm the mobile app and any partner feed are covered before you tighten thresholds.
  4. Sign queue tokens and verify them downstream. Entry-only checks protect the door and nothing behind it.
  5. Write the crawler allowlist first. Tighten detection after the allowlist exists, or you will lose visibility you did not know you depended on.

The cost of this work is a sprint or two of engineering time, most of it spent instrumenting. The cost of skipping it shows up as a sell-out that sold nothing, a resale market that took your margin, and customers who learned not to try next time. None of that appears in the launch dashboard, which is exactly why the problem survives from one drop to the next.

Send Us a Message