Technical Developers ๐Ÿ“… June 2026 โฑ 9 min read

How to Detect Bots with Browser Fingerprinting (2026)

Headless browsers, automation frameworks, and bots leak dozens of fingerprinting signals that real browsers don't. navigator.webdriver = true is just the start โ€” SwiftShader GPU, missing audio hardware, broken plugin arrays, and inconsistent Chrome runtime objects all reveal automation. Here's the complete technical guide to bot detection via browser fingerprinting.

Why Browser Fingerprinting Is Effective for Bot Detection

Bots โ€” scrapers, credential stuffers, ad fraud bots, account takeover tools โ€” typically run in headless browser environments. These environments differ from real user browsers in subtle but detectable ways. Fingerprinting reads dozens of JavaScript API values simultaneously, making it difficult for bots to spoof everything correctly at once.

Even if a bot patches the most obvious signals (like navigator.webdriver), there are always secondary signals that give it away โ€” inconsistencies between the claimed User Agent, the GPU renderer, the audio API state, and the plugin list.

Primary Bot Detection Signals

DEFINITIVE
navigator.webdriver === true
Set to true by Chrome/Firefox when controlled via WebDriver (Selenium, Puppeteer, Playwright). The single most reliable signal. Many bots patch this, but patching introduces other inconsistencies.
HIGH CONFIDENCE
SwiftShader / Mesa / llvmpipe GPU renderer
Headless Chrome uses Google's software renderer (SwiftShader) when no physical GPU is available. WebGL renderer string contains "SwiftShader", "Mesa", "llvmpipe", or "ANGLE" with software rasterizer โ€” not seen in real user browsers.
HIGH CONFIDENCE
navigator.plugins.length === 0
Real browsers have plugins (PDF Viewer, Chrome PDF Plugin, etc.). Headless Chrome returns an empty plugin array. A zero-length plugin list is a strong bot signal โ€” real users always have at least one plugin.
HIGH CONFIDENCE
window.chrome is undefined or incomplete
Real Chrome exposes a large window.chrome object with runtime, loadTimes, and other sub-objects. In headless mode, this object is missing or lacks key sub-properties that Chrome's browser extension system normally populates.
MEDIUM CONFIDENCE
AudioContext state: missing or suspended
Headless browsers often lack audio hardware. AudioContext.state returns "suspended" immediately on creation in headless environments. Real browsers start in "running" state when user interaction has occurred.
MEDIUM CONFIDENCE
Notification.permission not "default"
In headless browsers, Notification.permission returns "denied" immediately without any user action โ€” because there's no UI to prompt the user. Real browsers start at "default" and only change after a permission request.
MEDIUM CONFIDENCE
navigator.languages is empty or inconsistent
Bots often have an empty navigator.languages array or a language that doesn't match the navigator.language single value. Real browsers always have a consistent, non-empty languages list.

The Code: Basic Bot Detection Checks

function detectBot() {
  const signals = [];

  // 1. WebDriver flag
  if (navigator.webdriver) {
    signals.push('webdriver:true');
  }

  // 2. Headless Chrome: window.chrome check
  if (!window.chrome || !window.chrome.runtime) {
    signals.push('chrome_runtime:missing');
  }

  // 3. Empty plugins (headless indicator)
  if (navigator.plugins.length === 0) {
    signals.push('plugins:empty');
  }

  // 4. Software GPU renderer
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl');
  if (gl) {
    const ext = gl.getExtension('WEBGL_debug_renderer_info');
    if (ext) {
      const renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
      if (/SwiftShader|Mesa|llvmpipe|ANGLE/i.test(renderer)) {
        signals.push('gpu:software_renderer');
      }
    }
  }

  // 5. Notification permission auto-denied
  if (window.Notification && Notification.permission === 'denied') {
    signals.push('notifications:auto_denied');
  }

  // 6. Languages inconsistency
  if (!navigator.languages || navigator.languages.length === 0) {
    signals.push('languages:empty');
  }

  return {
    isBot: signals.length >= 2,
    confidence: signals.length / 6,
    signals
  };
}

const result = detectBot();
console.log(result);
// Real browser: { isBot: false, confidence: 0, signals: [] }
// Headless bot: { isBot: true, confidence: 0.67, signals: ['webdriver:true', 'plugins:empty', 'gpu:software_renderer', ...] }

Framework-Specific Detection

FrameworkPrimary SignalsPatches Used
Puppeteer webdriver, SwiftShader GPU, empty plugins puppeteer-extra-plugin-stealth patches most
Playwright webdriver, missing chrome.runtime, notifications:denied Chromium mode: fewer patches available
Selenium webdriver:true, cdc_ variables in DOM, __$webdriver_evaluate Older โ€” harder to fully patch
PhantomJS window.callPhantom, window._phantom, outdated UA Largely obsolete, easy to detect
Custom headless SwiftShader GPU most reliable, behavioral patterns Varies by implementation

Why Behavioral Signals Beat Static Checks

Sophisticated bots patch the obvious static signals. navigator.webdriver can be set to false. The plugin array can be spoofed. The chrome.runtime object can be mocked. But behavioral signals are much harder to fake:

UNDETECT.CLUB combines static fingerprint checks with CPU timing benchmarks to assess whether the browser is running on real hardware or a virtualized/containerized environment.

Frequently Asked Questions

Can browser fingerprinting reliably detect bots?
Browser fingerprinting is one of the most effective bot detection methods. Headless browsers leak dozens of signals: navigator.webdriver being true, missing plugins, SwiftShader/Mesa GPU, absent audio hardware, and behavioral patterns that differ from humans. Combining 5+ signals gives high confidence.
What is navigator.webdriver?
navigator.webdriver is a browser property set to true when the browser is controlled by automation software (Selenium, Puppeteer, Playwright). It's the simplest bot detection signal. Bot operators often try to set it to false, but other signals โ€” GPU renderer, plugin list, chrome.runtime โ€” can still reveal automation.
How do I check if my own browser looks like a bot?
UNDETECT.CLUB includes bot detection checks in its scan โ€” showing navigator.webdriver status, GPU renderer (SwiftShader indicates virtual/headless), plugin count, and automation flags. If any bot signals are flagged in your results, sites may be treating your browser as suspicious.

Check If Your Browser Looks Like a Bot

UNDETECT.CLUB runs full bot detection including webdriver flag, GPU renderer, plugin checks, and VM detection.

[ RUN BOT CHECK ]

Related Guides

Complete Guide
Browser Fingerprint Test โ€” What Sites Know About You
Deep Dive
AudioContext Fingerprinting โ€” The Hidden Tracker Explained