Resources Docs Free Blog Contact
Log inGet started
Back to Case Studies
Travel & Ticketing · Attack playbook
Mechanics and signals are what Maskbreak sees in production. The arithmetic below is a worked model — swap in your own inputs.

Scalper bots on limited inventory

The cheap seats disappear in seconds, checkout crawls for everyone else, and the same inventory reappears on a secondary market at a multiple. Nothing here is subtle — the difficulty is that every control aimed at the bots lands hardest on the customers, and the bots are the ones best equipped to pay the toll.

1
Residential IP per session — rate limits see nothing
<1s
Bot time-to-hold on release
~$0.002
Rough market price of one solved CAPTCHA
~125ms
Median server-side verdict latency
Threat signals detected
Headless Browser
Bot Automation
Residential Proxy

How the attack works

Inventory bots are a speed business. Two things are being bought: the ability to act faster than a human, and the ability to do it from enough identities that no per-anything limit binds.

  • Direct API calls against the same endpoints your own front end uses, skipping the interface entirely. Anything enforced only in the browser does not exist for them.
  • Residential proxy pools, one exit per session, so per-IP rate limiting has nothing to count.
  • CAPTCHA solving services, either farms of people or automated solvers, priced low enough per token that the challenge is a rounding error at bot volume.
  • Pre-warmed accounts with saved payment details, aged deliberately so account-age heuristics read them as established customers.

The economics are what make this durable. If the resale spread on a seat is €80, an operator can spend a lot per attempt and still print money.

Why CAPTCHAs and rate limiting make it worse

This is the part worth internalising, because the standard response actively helps the attacker.

A CAPTCHA costs the bot operator a fraction of a cent and costs your customer ten to thirty seconds and a meaningful abandonment probability. On a release where the constraint is speed, adding a uniform time cost to every participant advantages whoever can pay it fastest — which is the automation. You have not slowed the bots relative to humans; you have slowed humans relative to bots.

Per-IP rate limiting has the same shape. The bot has ten thousand addresses. Your customers share a handful: a corporate NAT, a mobile carrier range, a household. The limit binds on the customers and never on the operator.

Both controls are individually reasonable and become perverse against an adversary with a lower per-attempt cost than your users. That is worth checking before adding either.

What the verdict returns

Screened at the hold or add-to-basket step, which is where inventory is actually removed from sale:

{
  "decision": "block",
  "risk_score": 91,
  "reasons": [
    "automation_detected",
    "residential_proxy",
    "headless_browser"
  ],
  "network": { "proxy": true, "datacenter": false },
  "device": {
    "automation": true,
    "headless": true,
    "linked_accounts": 22
  }
}

device.automation and device.headless are statements about the client environment, not about request rate — which is why they fire on the first request rather than after a pattern has built. Against a bot that gets what it came for in under a second, a control that needs to observe a pattern has already lost.

Where to put the decision

At the hold, not at checkout. Inventory leaves availability the moment a seat is reserved, so a bot that is blocked at payment has still denied that seat to a customer for the length of the hold window. Screening at the reservation step is the difference between preventing the harm and documenting it.

Keep a challenge for the review band rather than removing it entirely. A customer on a VPN at a laptop solves it and continues; the point is that they are now a small fraction of sessions rather than all of them.

The arithmetic

A worked model. Replace the inputs with yours.

InputModel valueYours
Units per release exposed to bots1,200
Share taken by automation35%
Margin you lose per unit resold elsewhere€22
Releases per year12

1,200 × 35% × €22 × 12 = €110,880 a year in direct margin. The direct number is usually the smaller half. The second-order costs — infrastructure sized for bot load rather than customer load, checkout failures during releases, and the customer who stopped trying after the third sell-out — do not appear in this table and are frequently larger. If you can measure release-day conversion for customers who reached checkout and abandoned, that is the number to put next to this one.

Why WAFs and rate limiting fall short

A WAF is built for attacks against your application: injection, traversal, known exploit signatures. A scalper bot performs no attack at all. It makes well-formed, entirely legitimate requests to public endpoints, in the correct order, with valid sessions. There is no signature to match, because nothing is malformed.

That is the structural gap: the request is fine and the actor is the problem, which is a question about the client and its connection rather than about the payload. Keep the WAF for what it is good at, and put the actor question somewhere it can actually be asked.

Screen the hold step

Automation and residential proxy signals on the first request, before inventory leaves availability. Free tier: 1,000 requests per hour, no card.

Get Started Free
Fraud BriefOnce a month · no spam · unsubscribe anytime
Get the new VPN, proxy & bot patterns we see each month
Short, technical breakdowns of what fraudsters changed last month — written for engineers, not marketers.