Most 2010s "antibots" rely on IP lists: a giant database of suspicious addresses, and if a request comes from one - block. In 2026 this is a losing strategy: grey IP pools cost $0.001 per address, residential proxies give clean US IPs for $3/day. On top, marketplaces hand out rented IPs - Apple Private Relay, Google One VPN, iCloud Private Relay - and they look cleaner than office IPs.
IP blocking catches 60% of cheap bots but misses 95% of medium and advanced ones. JS-fingerprint looks at the browser itself - Canvas, WebGL, fonts, plugins, mouse behaviour - and builds a unique signature. Behavioural signals (timings, scrolling, clicks) form a second line of defence. Together they stop 99%+ of automated traffic.
Why IP blocking is outdated
Simple math: a datacenter IP costs $0.50-2/month, residential $3-8/day. A bot stealing conversions in an affiliate program with $5 payout earns enough on 1-2 successful leads to cover a day. IP banning is whack-a-mole - you have one hammer, the bot has 1.2 billion holes.
What pure IP lists also lose to:
- Mobile carriers - tens of thousands of users behind one NAT.
- Corporate networks - can't ban, real users inside.
- Apple Private Relay - anonymises IPs without traffic quality change.
- Headless browsers with residential proxy - look like a regular Mac user in Chicago.
What JS-fingerprint is
Idea: every browser is unique in its small details. Engine version, font set, GPU, audio stack, screen resolution, timezone, plugins. Individually banal, together - a fingerprint with 30+ bits of entropy. Enough to distinguish a billion users.
What we collect client-side:
// упрощённый набор сигналов
{
canvas: hash(canvasRender("TDS-probe", "Inter 16px")),
webgl: hash(gl.getParameter(gl.RENDERER) + gl.getParameter(gl.VENDOR)),
audio: hash(offlineAudioContext.fingerprint()),
fonts: detectAvailableFonts(['Inter', 'Roboto', ...]),
screen: { w, h, depth, dpr },
navigator: { ua, platform, lang, hwConcurrency, memory },
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
plugins: navigator.plugins.length,
webdriver: !!navigator.webdriver, // headless flag
permissions: queryPermissions(['notifications', 'geolocation'])
}
From these we hash into fp_id, kept for 7 days. If the same fp_id appears on 50 different IPs in an hour - it's not a user, it's a browser farm.
Behavioural signals
Fingerprint is static. Behaviour isn't. A headless browser doesn't move the mouse like a human, doesn't scroll with "jittery" gestures, doesn't pause on the page for 3 seconds before clicking. These signals give us a second layer:
| Signal | What we observe | Bot tells |
|---|---|---|
| mousemove | curve, acceleration | straight, instant |
| scroll | timings, deltas | uniform or absent |
| click | coords vs target | element center |
| focus/blur | time on page | < 500 ms |
| keypress | key intervals | constant or 0 |
Scoring and decisions
All signals combine into a 0-100 score. Decision isn't binary "bot/not" but a spectrum:
- 0-20: clean traffic, route to offer.
- 20-60: suspicious, send to white page.
- 60-90: probable bot - cloak or 404.
- 90-100: definite bot, reflect 200 OK to a blank page.
If we return 403, the bot operator notices the antibot and adjusts. If they get 200 OK on a blank page, they think traffic passed and keep going. We get the signal and save your affiliate program.
Method trade-offs
JS-fingerprint isn't perfect. Honest downsides:
- +1 round-trip: requires client-side JS, +20-40 ms latency. We mitigate via inline script and parallel loading.
- JS-disabled browsers: ~0.2% of traffic. Fall back to pure IP checks.
- Privacy modes: Brave, Firefox-strict, Tor - spoof fingerprints. This segment we judge by behaviour, not fingerprint.
- False positives: 0.3-0.5% of real users in 20-60 zone. Lost on white page but not as customers.
What we use in TDS
Under the hood - our own implementation, no external dependencies:
- Edge handler in Rust, 28 ms p95 from request to decision.
- IP database: 1.2B addresses with metadata (DC/ISP/mobile/VPN/TOR/residential), refreshed every 6 hours.
- Fingerprint: 24 signals, hashed into 64-bit
fp_id. - Behavioural: 6-event tracking (mouse, scroll, click, keypress, focus, blur) on the page.
- Scoring: gradient-boosted tree, trained on 100M clicks/week.
Last 30 days metrics across our traffic: false negative ≤ 0.8%, false positive ≤ 0.4%, mean decision time 28 ms p95.