Detect bots, headless browsers, and automation — in the browser and on the server.

One library. Three layers of defense. Zero external API keys.

Quick start · Detection modes · Signals · API · Examples · FAQ


Why this library?

Most bot-detection snippets are copy-pasted checks that rot quickly. detect-bot-client gives you a maintained, typed, testable toolkit that covers the full stack:

Live demo — run instant and behavioral checks in your browser.

Layer Runs where Catches
Instant Browser (sync) WebDriver, Selenium, Playwright, headless Chrome, bad WebGL/WebGPU
Behavioral Browser (over time) Robotic mouse/scroll/typing, synthetic events
Server Node / edge Datacenter IPs, AbuseIPDB, TLS fingerprint mismatch, timezone spoofing
  • No API keys — GeoIP and IP blocklists are bundled and updated weekly
  • TypeScript-first — full types, ESM + CJS
  • Composable — use one layer or combine all three
  • Explainable — every flag has a name, weight, and confidence level

Quick start

npm install detect-bot-client

Browser — block automation on page load

import { detectInstantClient } from "detect-bot-client";

const result = detectInstantClient(window);

if (!result.isLegitClient) {
  window.location.href = "/blocked";
}

Server — score a request in one call

import { detectServerClientAsync } from "detect-bot-client";

const result = await detectServerClientAsync({
  clientIp: req.ip,
  clientTimezone: req.headers["x-timezone"],
  userAgent: req.headers["user-agent"],
  tlsFingerprint: req.headers["x-ja3-hash"],
});

if (!result.isLegitClient) {
  return res.status(403).json({ signals: result.signals });
}

Behavioral — catch scripted interaction

import { createBehavioralClientDetector } from "detect-bot-client";

const result = await createBehavioralClientDetector({ context: window }).observe(10_000);

if (!result.isLegitClient) {
  console.warn("Robotic behavior", result.suspicionScore);
}

Detection modes

flowchart LR
  subgraph Browser
    A[Instant] --> B{Pass?}
    B -->|yes| C[Behavioral]
    B -->|no| X[Block]
    C --> D{Pass?}
    D -->|yes| E[Allow]
    D -->|no| X
  end
  subgraph Server
    S[detectServerClientAsync] --> T{Pass?}
    T -->|yes| E
    T -->|no| X
  end
  Browser -->|beacon + headers| Server
Mode API Speed Environment
Instant detectInstantClient Immediate Browser
Instant+ detectInstantClientAsync ~50ms Browser (adds WebGPU check)
Behavioral createBehavioralClientDetector 5–30s Browser
Server detectServerClientAsync ~1–5ms per IP Node, edge

Instant

Runs synchronously against window. Async variant adds WebGPU shader-f16 validation on Chromium.

const sync = detectInstantClient(window);
const async = await detectInstantClientAsync(window);

Behavioral

Observes mouse, scroll, and keyboard events. Score: 1 - Π(1 - weight) across triggered signals.

const detector = createBehavioralClientDetector({
  context: window,
  scoreThreshold: 0.55,
  onUpdate: (r) => console.log(r.suspicionScore),
});
await detector.observe(8_000);

Server

Pass clientIp to auto-run GeoIP lookup, datacenter range check, AbuseIPDB blocklist, iCloud Private Relay check, TLS validation, and timezone comparison.

const result = await detectServerClientAsync({
  clientIp: req.ip,
  clientTimezone: req.headers["x-timezone"],
  tlsFingerprint: req.headers["x-ja3-hash"],
  userAgent: req.headers["user-agent"],
});

Bundled IP data is refreshed weekly. Run locally: npm run update:ip-data.


Signals

Instant (boolean flags)

Any suspicious flag fails isLegitClient.

Flag Triggers when
isWebDriver navigator.webdriver === true
isPhantomJS PhantomJS globals present
isNightmare Nightmare.js marker
isSelenium Selenium document markers
isDomAutomation Chrome DOM automation globals
isHeadless WebDriver or HeadlessChrome UA
isSuspiciousResolution Screen < 136×170
isUserAgentValid UA does not start with Mozilla/5.0 (
isWebGLSupported No WebGL context
isModern Below Chrome 121 / Firefox 128 / Safari 16.4
isMissingChromeObject Chromium without chrome.runtime
isSoftwareRenderer SwiftShader / llvmpipe WebGL
isSuspiciousWindowDimensions No browser chrome + origin placement
isEmptyPlugins Zero plugins on Chromium
isAutomationArtifacts ChromeDriver / Playwright markers
isSuspiciousWebDriverDescriptor Patched navigator.webdriver
isShaderF16Supported Async — missing WebGPU shader-f16 on Chromium

Behavioral (weighted)

ID Weight Confidence Description
no-mouse-activity 0.20 low Clicks without mouse moves
click-without-mouse-movement 0.35 high Click with no recent mouse path
linear-mouse-movement 0.25 medium Straight path, uniform speed
teleport-mouse 0.40 high Implausible cursor jumps
linear-scroll 0.30 medium Uniform scroll deltas/timing
linear-typing 0.35 high Robotic or superhuman intervals
synthetic-events 0.50 high isTrusted === false

Server (weighted)

ID Weight Confidence Description
timezone-mismatch 0.50 high Client TZ ≠ GeoIP TZ
known-suspicious-tls 0.55 high JA3 matches Python/curl/Go/Java
tls-user-agent-mismatch 0.50 high JA3 conflicts with User-Agent
missing-tls-fingerprint 0.25 medium Browser UA without JA3
accept-language-geo-mismatch 0.20 low Accept-Language missing GeoIP country
datacenter-browser-mismatch 0.35 medium Datacenter IP + browser UA
abuse-listed-ip 0.60 high AbuseIPDB 30-day blocklist
icloud-private-relay 0.15 low iCloud Private Relay egress

Bundled IP data: data/datacenter_ip_ranges.csv (ipcat), data/abuse_ip_db_30d_ips.csv (AbuseIPDB), data/icloud_private_relay_ip_ranges.csv (Apple).


API

import {
  // Browser — instant
  detectInstantClient,
  detectInstantClientAsync,

  // Browser — behavioral
  createBehavioralClientDetector,
  analyzeBehavioralSamples,

  // Server
  detectServerClient,
  detectServerClientAsync,
  enrichServerContext,
  lookupClientIpGeo,
  createIpListChecker,

  // Standalone checks
  isAutomationArtifacts,
  isSoftwareRenderer,
  isTimezoneMismatch,
  isTlsUserAgentMismatch,
  KNOWN_SUSPICIOUS_TLS_FINGERPRINTS,
} from "detect-bot-client";

Server options

detectServerClientAsync(context, {
  dataDir: "./custom-data",
  lookupGeo: true,
  checkIpLists: true,
  timezoneToleranceMinutes: 60,
  scoreThreshold: 0.5,
  requireTlsFingerprint: false,
  suspiciousTlsFingerprints: [],
});

Behavioral options

createBehavioralClientDetector({
  context: window,
  minObservationMs: 3_000,
  scoreThreshold: 0.55,
  pollIntervalMs: 1_000,
  onUpdate: (result) => {},
});

Examples

Defense in depth

const instant = detectInstantClient(window);
if (!instant.isLegitClient) block();

fetch("/api/beacon", {
  headers: { "X-Timezone": Intl.DateTimeFormat().resolvedOptions().timeZone },
});

const behavioral = await createBehavioralClientDetector({ context: window }).observe(10_000);
if (!behavioral.isLegitClient) challenge();

const server = await detectServerClientAsync({ clientIp: req.ip /* ... */ });
if (!server.isLegitClient) return res.status(403).end();

Express middleware

import { detectServerClientAsync } from "detect-bot-client";

app.use(async (req, res, next) => {
  const result = await detectServerClientAsync({
    clientIp: req.ip,
    clientTimezone: req.headers["x-timezone"],
    userAgent: req.headers["user-agent"],
    tlsFingerprint: req.headers["x-ja3-hash"],
  });

  if (!result.isLegitClient) {
    return res.status(403).json({ signals: result.signals });
  }
  next();
});

Next.js client guard

"use client";
import { useEffect } from "react";
import { detectInstantClient } from "detect-bot-client";

export function BotGuard({ children }) {
  useEffect(() => {
    if (!detectInstantClient(window).isLegitClient) {
      window.location.href = "/blocked";
    }
  }, []);
  return children;
}

FAQ

Can client-side checks be bypassed? Yes. Use instant + behavioral for friction; server detection for authoritative decisions.

False positives? Possible with privacy browsers, VPNs, iCloud Relay, corporate proxies, VMs. Tune scoreThreshold.

How often is IP data updated? Weekly (Mondays 04:00 UTC). Run npm run update:ip-data locally anytime.

Works without bundlers? Yes — ESM + CJS + types. Use with Vite, Webpack, Next.js, or esm.sh.


Development

git clone https://github.com/okasi/detect-bot-client.git
cd detect-bot-client
npm install
npx patchright install chromium   # once, for browser tests
npm test                          # unit tests
npm run test:patchright           # real Chromium via patchright
npm run build
npm run build:site          # copy browser bundle into docs/ for GitHub Pages

Live demo: https://okasi.github.io/detect-bot-client/ (deployed from docs/ on push to main).

GitHub Pages setup (one time): Settings → Pages → Build and deployment → Deploy from a branch → Branch: gh-pages / / (root).

Publish to npm

npm package: detect-bot-client (verified available; distinct from older detect-bot).

Step 1 — First publish (once, from your computer)

git clone https://github.com/okasi/detect-bot-client.git
cd detect-bot-client
npm install
npm run test
npm run build
npm login
npm publish --access public

Step 2 — Enable Trusted Publishing (for GitHub Actions)

  1. https://www.npmjs.com/package/detect-bot-clientSettingsTrusted publishing
  2. GitHub Actions → user okasi, repo detect-bot-client, workflow publish.yml
  3. Save

Step 3 — Future releases via Actions

npm version patch
git push origin main --follow-tags

Or re-run Actions → Publish npm → Run workflow.

See AGENTS.md for architecture and contributor guidance.