Behavioral biometrics in bot detection means scoring how a visitor moves, scrolls and types to decide whether a person is present. Anti-bot scripts record these events on every page and feed them to a model, so most bots are judged by their behavior long before a CAPTCHA appears.
TL;DR
- Vendors record pointer, scroll, key and focus events with timestamps and score the session continuously. No challenge is shown unless the score is bad.
- Scripted input fails on physics, not on appearance: straight lines, constant velocity, zero overshoot, and events that arrive at exact intervals.
- Synthetic DOM events are flagged by
isTrusted: false. Input injected through the automation protocol is trusted, but still has to look human. - Human motion follows known laws (Fitts' law for target acquisition, minimum-jerk velocity profiles). Vendors fit the recorded motion against these.
- Replaying one human recording across many sessions is caught by exact repetition. Variability is the signal, not realism.
- 2026 detection write-ups rank behavior as the hardest layer to defeat, and Cloudflare now trains per-site models with adaptive thresholds, so the "human" baseline is the specific site's visitors rather than a global one. AI browsing agents, which click by coordinates from screenshots and never idle, are the newest and easiest class to separate.
Human versus script, one movement
xychart-beta
accTitle: Pointer speed over one reach, human versus script
accDescr: A human reach speeds up, peaks before the midpoint and slows near the target, while a scripted ten-step move holds one constant speed and stops.
title "Pointer speed over one reach (normalized)"
x-axis "time" [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
y-axis "speed" 0 --> 10
line "human" [0, 2, 5, 8, 9.5, 8, 5, 2.5, 1, 1.5, 0]
line "scripted, 10 steps" [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0]
A human reach accelerates, peaks before the midpoint, decelerates, and often corrects near the target. A linear interpolation moves at one speed and stops. Vendors do not need to see the whole session to tell these apart; one reach is enough.
What is collected
The client script attaches listeners for mousemove, mousedown, click, wheel, scroll, keydown, keyup, touchstart, touchmove, focus, blur and visibilitychange. Each event is stored with its coordinates and a high resolution timestamp, batched, and posted to the vendor endpoint alongside the fingerprint. The server computes features per session and feeds a model.
The important point is that this happens on every page, continuously, for every visitor. The score is a property of the session, updated as it goes. A CAPTCHA, when it appears, is the fallback for an undecided score, not the primary mechanism.
Features that separate scripts from people
Path geometry. A human pointer moving to a button does not travel in a straight line. It accelerates, curves, decelerates near the target, and often overshoots slightly and corrects. The velocity profile over time is bell shaped. A script that calls page.mouse.move(x, y) once jumps instantly. A script that interpolates linearly produces a straight line at constant speed, which no human produces.
Timing. Human events arrive at the display's refresh cadence with jitter. Scripted events arrive at whatever interval the loop uses, often exact multiples of a sleep value. Keystroke intervals for humans vary by key pair and are never identical across a word; scripted typing has a fixed delay or a delay from a uniform random distribution, which is distinguishable from the human log-normal distribution.
Sequence. Humans move the pointer before clicking, scroll before reaching content, and hover over things they do not click. Scripts click elements that were never under the pointer, scroll in exact page heights, and never pause on content.
Absence. A session with page loads, form submissions and no pointer or scroll events at all is the most common bot signature. Many scrapers never generate input events because they only read the DOM.
Trusted versus synthetic events
Events created in JavaScript with new MouseEvent(...) and dispatchEvent carry isTrusted: false. Detectors check this on the first click. Input sent through the Chrome DevTools Protocol (Input.dispatchMouseEvent) or through WebDriver's actions API goes through the browser's input pipeline and is trusted. Automation frameworks use that path, which removes the flag but not the physics problem above.
Why "humanizer" libraries half work
Libraries exist that generate Bezier curve paths with randomized control points and velocity. These defeat the simplest geometric features. They are weaker on the second order properties: the velocity profile shape, the overshoot and correction behavior, and the relationship between distance and movement duration (Fitts' law predicts duration from distance and target size, and real humans fit it). They are also deterministic in the wrong way: the random generator's distribution is uniform while human variability is not.
The deeper issue is that vendors train on their own traffic. A path generator that passes today's model is a labeled negative example in tomorrow's training set once sessions using it are blocked for other reasons. Cloudflare's 2026 update makes this explicit: models learn each domain's own traffic patterns and set per-domain trust thresholds, so a generator tuned against a demo page is tuned against the wrong distribution.
Agents behave like agents
The largest new population since 2025 is autonomous browsing agents: Browser Use, Operator, Skyvern, and the browser modes of the assistants. They interact in a distinctive way: a screenshot, a model call, a single action, a pause of one to several seconds, repeat. Clicks land at integer coordinates derived from element bounding boxes or vision output.
There is no hover, no idle wander, no scroll-to-read. The June 2026 agent-attribution study found interaction behavior combined with network features identified the framework at 97% accuracy. This is not a problem the agent frameworks are trying to solve by faking humanity; the direction instead is to identify themselves openly through signed requests, covered in the anti-bot client script post.
Demonstration: plot a human path against a scripted one
Serve this page locally. Move your pointer from the left box to the right box and click. Then drive the same page with Playwright using page.mouse.move in ten linear steps and page.mouse.click.
<div id="a" style="position:absolute;left:40px;top:200px;width:60px;height:60px;background:#48c"></div>
<div id="b" style="position:absolute;left:700px;top:260px;width:60px;height:60px;background:#c84"></div>
<pre id="out"></pre>
<script>
const pts = [];
addEventListener('mousemove', e => pts.push([e.clientX, e.clientY, performance.now(), e.isTrusted]));
document.getElementById('b').addEventListener('click', () => {
const v = [];
for (let i = 1; i < pts.length; i++) {
const [x0, y0, t0] = pts[i - 1], [x1, y1, t1] = pts[i];
v.push(Math.hypot(x1 - x0, y1 - y0) / Math.max(t1 - t0, 0.01));
}
const mean = v.reduce((a, b) => a + b, 0) / v.length;
const sd = Math.sqrt(v.reduce((a, b) => a + (b - mean) ** 2, 0) / v.length);
const dts = pts.slice(1).map((p, i) => p[2] - pts[i][2]);
const uniqueDt = new Set(dts.map(d => Math.round(d))).size;
document.getElementById('out').textContent = JSON.stringify({
samples: pts.length,
trusted: pts.every(p => p[3]),
velocityCV: +(sd / mean).toFixed(2),
distinctIntervals: uniqueDt,
peakVelocityAt: +(v.indexOf(Math.max(...v)) / v.length).toFixed(2),
}, null, 2);
});
</script>
Playwright driver for the scripted run:
await page.mouse.move(70, 230);
await page.mouse.move(730, 290, { steps: 10 });
await page.mouse.click(730, 290);
console.log(await page.locator('#out').textContent());
Expected results:
| Metric | Human | Scripted (10 steps) |
|---|---|---|
| samples | 40 to 120 | 10 to 11 |
| trusted | true | true |
| velocityCV (variation) | 0.5 to 1.2 | near 0 |
| distinctIntervals | many | 1 or 2 |
| peakVelocityAt | 0.3 to 0.5 (bell shaped) | undefined or 0 |
The scripted run is trusted and still obvious. Add a Bezier humanizer and re-run: velocityCV rises, distinctIntervals rises, and peakVelocityAt may land in the human range. Then run it ten times and compare the ten outputs. If they share the same control points, the ten paths are near-identical, and that repetition is the next feature a vendor computes.
Read more: the motor-control models vendors borrow
- Fitts' law: movement time is proportional to log2(distance / target width + 1). Human sessions cluster on this line; scripts with fixed durations do not.
- Minimum-jerk model: human point-to-point reaches follow a velocity profile that minimizes the derivative of acceleration, producing a symmetric bell. Deviations from it are a feature.
- Submovement structure: real reaches consist of a primary ballistic movement and one or two corrective submovements near the target. Overshoot and correction counts are features.
- Scroll dynamics: wheel events on a physical mouse arrive in fixed delta steps; trackpads produce fractional deltas with inertial decay. A "scroll" that jumps
window.innerHeightexactly matches neither device.
Read more: what this means for scrapers that only read the DOM
A scraper that loads pages and extracts content without any input events is not pretending to be a human, and some vendors treat that honestly: a session that never interacts and fetches at a modest rate may be classified as a benign crawler rather than a fraud bot, with a different policy. The problems start when such a scraper also claims a Chrome identity with a residential IP and session cookies, because then the absence of behavior contradicts the rest. Consistency across layers applies to behavior too. A browser extension running inside a session where the user actually scrolled and clicked has that consistency for free.
Frequently asked questions
How do websites detect bots from mouse movement?
They record pointer positions with timestamps and compute features such as path curvature, speed profile, overshoot and timing jitter. Human reaches accelerate, peak and slow down near the target, while scripted moves are straight, evenly timed or instantaneous.
Are synthetic JavaScript events detected?
Yes. Events created in JavaScript with dispatchEvent carry isTrusted set to false, and detectors check it on the first interaction. Input sent through the browser's automation protocol is trusted, but it still has to move like a person to pass behavioral scoring.
Can mouse humanizer libraries beat behavioral detection?
Partly, and not for long. They defeat simple geometric checks but tend to miss second-order features such as the velocity shape and the link between distance and duration. Vendors also train on their own traffic, so any widely used generator becomes a known pattern.
How do AI browsing agents behave differently from people?
Agents work in a loop of screenshot, model call and one action, with pauses of seconds and clicks at element coordinates. They do not hover, wander or scroll to read, which makes them easy to tell apart from people.