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.

TL;DR

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.
Why "reflect" instead of "block"

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.