Bot detection projects usually begin by gathering everything measurable and end with a dashboard nobody looks at. The reason is that the signals were chosen for availability rather than for consequence, and a signal you would never act on is a cost with no benefit attached.
Invert it. Decide first what responses you are willing to take — allow, challenge, degrade, refuse — and then ask which signals justify each one. That question eliminates most of the list immediately.
Cheap signals with real information
The most useful early signals are structural rather than behavioural. A form field hidden from users but filled in by a naive script. A submission that arrives faster than a human could read the page. A request whose declared client contradicts the way it actually connects. None of these require watching people, and all of them separate low-effort automation from users.
Network reputation is the other cheap one, with a caveat. Traffic from data centre address space is not a person browsing, and treating it as suspicious for a sign-in flow is reasonable. Treating a shared residential or corporate address as suspicious is not, because you will be refusing an entire office.
- Honeypot fields and timing floors — cheap, privacy-neutral, catch the low end
- Consistency between declared client and observed connection behaviour
- Address type: data centre versus consumer, not address reputation alone
- Attempt shape: how many accounts, how fast, in what order
A signal you would never act on is a cost with no benefit. Pick the response first.
The expensive signals, and what they cost
Device fingerprinting and behavioural biometrics are more discriminating and carry obligations. They collect data about people to make a judgement about them, which brings them inside your privacy notice, your retention policy and your data processing agreements. That is a real cost, and it should buy a proportionate benefit.
Visible challenges have a cost too, paid by users. They fail people using assistive technology, they fail on slow connections, and every one shown to a legitimate person is friction they did not deserve. Reserve them for the cases where cheaper signals already suggest trouble, rather than showing them to everybody by default.
Graduated response, and knowing when you are wrong
Binary allow-or-block forces every signal to be certain, which none of them are. A graduated response absorbs uncertainty: a slightly odd context gets a slower path, an odder one gets a challenge or a required second factor, and only a very confident judgement refuses outright. Most traffic never notices, and the borderline cases stay recoverable.
Then measure the mistakes, because the failures of a detection system are invisible by construction. A refused legitimate user does not file a bug, they leave. Keep a sampled log of what was challenged and what happened next, give support a way to see why a person was blocked, and run new rules in observation mode before they can refuse anybody.
- Score and graduate; reserve refusal for high confidence
- Ship every new rule in observation mode first and read the sample
- Give support a why-was-this-blocked view; without it, tickets are unanswerable
- Never let a detection decision be invisible to the audit trail
Everything here, already built
Sign-in, enterprise SSO, directory provisioning, roles and an audit trail behind one API. Start with the quickstart and have a working sign-in this afternoon.