How Do Websites Detect Headless Browsers in 2026?
Understanding headless browser detection methods is essential for anyone working in browser automation, web scraping, or multi-account management. Whether you’re building stealth automation or defending your own site from bot traffic, knowing exactly how detection works gives you a decisive advantage.
In 2026, headless browser detection has evolved far beyond simple navigator.webdriver checks. Modern anti-bot platforms — Cloudflare Turnstile, Akamai Bot Manager, DataDome, PerimeterX (now HUMAN), and Kasada — deploy multi-layered detection that combines JavaScript environment analysis, hardware fingerprinting, network profiling, and behavioral biometrics.
This technical guide dissects every major detection vector with code examples, explains why each method works, and shows you how to test your own browser sessions against them. We’ll also cover which detection methods are most difficult to evade and why cloud-based antidetect browsers have become the definitive solution for bypassing all of them.
Detection Vector #1: navigator.webdriver Flag
The navigator.webdriver property is the oldest and most fundamental headless browser detection method. When a browser is controlled by WebDriver (Selenium, Playwright, or Puppeteer via CDP), this flag is automatically set to true.
Detection code:
// Basic webdriver detection
if (navigator.webdriver === true) {
console.log('BOT DETECTED: navigator.webdriver is true');
}
// Advanced: check if the property has been tampered with
const descriptor = Object.getOwnPropertyDescriptor(navigator, 'webdriver');
if (descriptor && descriptor.get && descriptor.get.toString().includes('native code') === false) {
console.log('BOT DETECTED: navigator.webdriver has been overridden');
}
// Even more advanced: prototype chain check
const proto = Object.getPrototypeOf(navigator);
const protoDescriptor = Object.getOwnPropertyDescriptor(proto, 'webdriver');
if (!protoDescriptor || protoDescriptor.value !== undefined) {
console.log('BOT DETECTED: webdriver property prototype mismatch');
}
Modern anti-bot scripts don’t just check the value — they check how the property is defined. In a real browser, webdriver is defined on the Navigator.prototype with a native getter. Stealth plugins that override it with Object.defineProperty on the navigator instance (not the prototype) leave a detectable fingerprint. The property descriptor will show a non-native getter function, or the property will exist on the wrong object in the prototype chain.
Detection Vector #2: Chrome DevTools Protocol (CDP) Artifacts
CDP is the control channel used by Playwright, Puppeteer, and Chrome DevTools itself. While it’s invisible at the JavaScript level in theory, enabling CDP domains creates observable side effects. This is one of the most sophisticated headless browser detection methods in active use.
Runtime.enable detection:
// When CDP Runtime.enable is active, error stack traces behave differently
function detectCDPRuntime() {
const error = new Error();
// CDP-controlled browsers may include extra internal frames
const stack = error.stack;
if (stack && stack.includes('Runtime.evaluate')) {
return true;
}
// Check for console API being overridden by CDP
const consoleLog = console.log.toString();
if (!consoleLog.includes('[native code]')) {
return true;
}
return false;
}
Page.addScriptToEvaluateOnNewDocument detection:
// Scripts injected via CDP execute in a special isolated world
// that can be detected through timing analysis
function detectInjectedScripts() {
const start = performance.now();
// In a CDP-controlled browser, certain operations that interact
// with injected scripts will have measurable latency
const result = navigator.webdriver;
const end = performance.now();
// Extremely fast property access (~0ms) suggests a pre-compiled
// native getter. Slight delay suggests a JavaScript override
if (end - start > 0.01) {
return 'SUSPICIOUS: Property access took too long';
}
return 'CLEAN';
}
Debugger detection:
// If Chrome DevTools or a CDP debugger has been attached,
// the debugger statement behaves differently
function detectDebugger() {
const before = Date.now();
debugger; // This pauses if a debugger is attached
const after = Date.now();
// If more than 100ms elapsed, a debugger is likely attached
if (after - before > 100) {
return true;
}
return false;
}
For anyone trying to achieve automation without detection, understanding these CDP artifacts is critical — they’re the detection vectors that stealth plugins struggle most to address.
Detection Vector #3: Missing Plugins and Languages
A legitimate Chrome installation on Windows comes with a specific set of plugins and MIME types. Headless Chrome and automated Chrome instances frequently have empty or inconsistent plugin arrays.
Detection code:
// Plugin enumeration check
function checkPlugins() {
const plugins = navigator.plugins;
const results = {};
results.pluginCount = plugins.length;
results.hasChromePDF = Array.from(plugins).some(p =>
p.name.includes('Chrome PDF')
);
// Real Chrome typically has 5 plugins
// Headless Chrome historically had 0
if (plugins.length === 0) {
results.verdict = 'HEADLESS: No plugins found';
} else if (plugins.length < 3) {
results.verdict = 'SUSPICIOUS: Too few plugins';
}
// Check MIME types match plugin declarations
const mimeTypes = navigator.mimeTypes;
results.mimeTypeCount = mimeTypes.length;
if (plugins.length > 0 && mimeTypes.length === 0) {
results.verdict = 'BOT: Plugins exist but no MIME types';
}
return results;
}
// Language consistency check
function checkLanguages() {
const lang = navigator.language;
const langs = navigator.languages;
if (!langs || langs.length === 0) {
return 'HEADLESS: No languages defined';
}
if (langs[0] !== lang) {
return 'SUSPICIOUS: language mismatch with languages[0]';
}
// Check against Accept-Language header (requires server cooperation)
return 'OK';
}
Modern stealth plugins spoof the plugins array, but they often get the details wrong — the MIME type associations, the filename properties, or the description strings may not match what a real Chrome binary reports. Anti-bot systems maintain databases of legitimate plugin configurations per Chrome version and flag mismatches.
Detection Vector #4: WebGL Renderer Strings
WebGL detection is one of the most reliable headless browser detection methods because it probes the actual GPU hardware (or lack thereof) being used by the browser.
Detection code:
function getWebGLInfo() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
return { verdict: 'SUSPICIOUS: WebGL not available' };
}
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const result = {};
if (debugInfo) {
result.vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
result.renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
}
// Known headless/automation indicators
const suspiciousRenderers = [
'Google SwiftShader', // Software renderer used in headless
'llvmpipe', // Mesa software renderer (Linux headless)
'Microsoft Basic Render' // Windows fallback renderer
];
if (suspiciousRenderers.some(r => result.renderer?.includes(r))) {
result.verdict = 'HEADLESS: Software renderer detected';
}
// Check for ANGLE (common in real Chrome on Windows/Mac)
if (result.renderer?.includes('ANGLE')) {
// ANGLE is normal, but check vendor consistency
if (!result.vendor?.includes('Google')) {
result.verdict = 'SUSPICIOUS: ANGLE with wrong vendor';
}
}
// Collect supported extensions for fingerprinting
result.extensions = gl.getSupportedExtensions();
result.extensionCount = result.extensions?.length || 0;
// Real GPU-accelerated Chrome typically supports 30+ extensions
// Software renderers support significantly fewer
if (result.extensionCount < 20) {
result.verdict = 'SUSPICIOUS: Too few WebGL extensions';
}
return result;
}
Headless Chrome uses Google SwiftShader as its GPU backend, which produces a completely different set of WebGL capabilities compared to a real NVIDIA, AMD, or Intel GPU. Even if you spoof the renderer string, the actual rendering output and supported extension list will still differ from what a real GPU produces.
Detection Vector #5: Canvas Fingerprinting Inconsistencies
Canvas fingerprinting renders specific graphics (text, shapes, gradients) and hashes the pixel output. The hash is unique per browser+OS+GPU combination. Headless browsers produce different hashes than headed browsers because of rendering pipeline differences.
Detection code:
function getCanvasFingerprint() {
const canvas = document.createElement('canvas');
canvas.width = 280;
canvas.height = 60;
const ctx = canvas.getContext('2d');
// Render text with specific fonts and effects
ctx.textBaseline = 'alphabetic';
ctx.fillStyle = '#f60';
ctx.fillRect(100, 1, 62, 20);
ctx.fillStyle = '#069';
ctx.font = '11pt "Times New Roman"';
ctx.fillText('Cwm fjordbank', 2, 15);
// Add gradient for more uniqueness
const gradient = ctx.createLinearGradient(0, 0, 280, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'green');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillText('Detection test ☺', 4, 45);
const dataURL = canvas.toDataURL();
// Hash comparison against known fingerprint databases
// Headless browsers produce distinctly different output
return {
hash: simpleHash(dataURL),
dataLength: dataURL.length
};
}
// Check for canvas poisoning (stealth tool indicator)
function detectCanvasPoisoning() {
const canvas1 = document.createElement('canvas');
const canvas2 = document.createElement('canvas');
const ctx1 = canvas1.getContext('2d');
const ctx2 = canvas2.getContext('2d');
// Draw identical content
ctx1.fillText('test', 10, 10);
ctx2.fillText('test', 10, 10);
const data1 = canvas1.toDataURL();
const data2 = canvas2.toDataURL();
// Real browsers produce identical output for identical operations
// Some stealth tools add random noise to canvas output
if (data1 !== data2) {
return 'BOT DETECTED: Canvas output is non-deterministic (noise injection)';
}
return 'OK';
}
The canvas poisoning detection is particularly clever — some antidetect tools add random noise to canvas output to prevent fingerprinting, but this noise itself becomes a detection signal because legitimate browsers always produce deterministic canvas output for identical operations.
Detection Vector #6: Timing Attacks and Performance Profiling
Timing-based detection exploits the fact that headless browsers and CDP-controlled browsers exhibit different performance characteristics than user-driven browsers.
Detection code:
async function timingAnalysis() {
const results = {};
// 1. requestAnimationFrame timing
// Headless browsers may not properly implement vsync-locked rAF
const rafTimings = [];
await new Promise(resolve => {
let count = 0;
function frame(timestamp) {
rafTimings.push(timestamp);
count++;
if (count < 10) requestAnimationFrame(frame);
else resolve();
}
requestAnimationFrame(frame);
});
// Real browsers produce ~16.67ms intervals (60fps vsync)
// Headless may produce irregular intervals
const intervals = [];
for (let i = 1; i < rafTimings.length; i++) {
intervals.push(rafTimings[i] - rafTimings[i - 1]);
}
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
if (avgInterval < 10 || avgInterval > 25) {
results.rAF = 'SUSPICIOUS: Abnormal frame timing';
}
// 2. Performance.now() precision
// Some privacy tools or headless modes reduce timer precision
const times = new Set();
for (let i = 0; i < 100; i++) {
times.add(performance.now());
}
if (times.size < 50) {
results.timerPrecision = 'SUSPICIOUS: Reduced timer precision';
}
// 3. DOM operation benchmarks
// CDP overhead can make DOM operations measurably slower
const domStart = performance.now();
for (let i = 0; i < 1000; i++) {
document.createElement('div');
}
const domEnd = performance.now();
results.domCreationTime = domEnd - domStart;
return results;
}
Timing attacks are difficult to defend against because they measure intrinsic properties of the browser execution environment rather than spoofable JavaScript values. A headless browser running on a powerful server produces DOM operations significantly faster than a real laptop browser, and this speed difference is itself a detection signal.
Detection Vector #7: Mouse Movement and Behavioral Analysis
Behavioral biometrics represent the cutting edge of headless browser detection methods. These systems analyze how users interact with the page, not just what the browser reports about itself.
Detection approach:
class BehaviorAnalyzer {
constructor() {
this.mouseEvents = [];
this.keyEvents = [];
this.scrollEvents = [];
document.addEventListener('mousemove', e => {
this.mouseEvents.push({
x: e.clientX, y: e.clientY,
t: performance.now()
});
});
document.addEventListener('keydown', e => {
this.keyEvents.push({
key: e.key, t: performance.now()
});
});
}
analyzeMouseMovement() {
if (this.mouseEvents.length < 10) {
return { verdict: 'SUSPICIOUS: No mouse movement detected' };
}
// Calculate movement entropy
let totalDistance = 0;
let straightLineDistance = 0;
const velocities = [];
for (let i = 1; i < this.mouseEvents.length; i++) {
const prev = this.mouseEvents[i - 1];
const curr = this.mouseEvents[i];
const dx = curr.x - prev.x;
const dy = curr.y - prev.y;
const dt = curr.t - prev.t;
const dist = Math.sqrt(dx * dx + dy * dy);
totalDistance += dist;
if (dt > 0) velocities.push(dist / dt);
}
// Human mouse movements have variable velocity
// Bots often have constant velocity (linear interpolation)
const velocityVariance = this.calculateVariance(velocities);
if (velocityVariance < 0.1) {
return { verdict: 'BOT: Mouse velocity too uniform' };
}
// Check for impossibly straight lines
// Humans produce curved paths with micro-corrections
const first = this.mouseEvents[0];
const last = this.mouseEvents[this.mouseEvents.length - 1];
straightLineDistance = Math.sqrt(
(last.x - first.x) ** 2 + (last.y - first.y) ** 2
);
const curvatureRatio = totalDistance / straightLineDistance;
if (curvatureRatio < 1.05) {
return { verdict: 'BOT: Mouse path too straight' };
}
return { verdict: 'HUMAN-LIKE', curvatureRatio, velocityVariance };
}
calculateVariance(arr) {
const mean = arr.reduce((a, b) => a + b, 0) / arr.length;
return arr.reduce((sum, val) => sum + (val - mean) ** 2, 0) / arr.length;
}
}
The behavioral layer is particularly challenging because tools like Playwright’s page.click() teleport the cursor directly to the element without any preceding movement. Even libraries that generate fake mouse paths often produce paths with insufficiently variable velocity profiles or unnaturally straight trajectories.
Detection Vector #8: CDP Runtime Domain Checks
This is one of the most advanced detection techniques used by enterprise anti-bot systems. It specifically targets the CDP control channel used by automation frameworks.
Detection approach:
// Detect if Runtime.evaluate is being used
function detectRuntimeEvaluate() {
// When code is executed via CDP Runtime.evaluate,
// the execution context differs from page-level code
// Check 1: Error stack trace analysis
try {
throw new Error('stack trace test');
} catch (e) {
const stack = e.stack;
// CDP-evaluated code may have different source URLs
if (stack.includes('__puppeteer_evaluation_script__') ||
stack.includes('pptr:/') ||
stack.includes('__playwright_evaluation_script__')) {
return 'BOT DETECTED: Automation framework stack trace';
}
}
// Check 2: Source URL of the current script
// Page scripts have proper URLs, CDP-injected scripts don't
try {
const script = document.currentScript;
if (script === null && !document.readyState === 'loading') {
return 'SUSPICIOUS: No currentScript in a non-loading document';
}
} catch (e) {}
return 'OK';
}
// Detect if Page.addScriptToEvaluateOnNewDocument was used
function detectInjectedWorldScripts() {
// Scripts added via CDP run in the main world but before page scripts
// This can be detected by checking execution order
// If certain properties were modified before any page script ran,
// it suggests pre-injection via CDP
const webdriverDescriptor = Object.getOwnPropertyDescriptor(
Navigator.prototype, 'webdriver'
);
if (webdriverDescriptor &&
typeof webdriverDescriptor.get === 'function' &&
!webdriverDescriptor.get.toString().includes('[native code]')) {
return 'BOT DETECTED: webdriver property has non-native getter';
}
return 'OK';
}
For comprehensive strategies on beating these detection systems, our guide to bypassing anti-bot systems covers both the offensive and defensive perspectives.
Detection Vector Summary: What Works Against What
| Detection Method | Catches | Difficulty to Evade | Stealth Plugin Bypass? | Antidetect Browser Bypass? |
|---|---|---|---|---|
| navigator.webdriver | All automation frameworks | Easy | ✅ Yes | ✅ Yes |
| CDP artifacts | Puppeteer, Playwright, CDP clients | Hard | ⚠️ Partial | ✅ Yes |
| Missing plugins/languages | Headless Chrome, misconfigured profiles | Medium | ✅ Yes | ✅ Yes |
| WebGL renderer | Headless, VMs without GPU passthrough | Hard | ⚠️ Spoofed only | ✅ Hardware-backed |
| Canvas fingerprint | Headless, noise-injecting stealth tools | Hard | ⚠️ Can backfire | ✅ Consistent |
| Timing attacks | Headless (fast execution), VMs | Very Hard | ❌ No | ✅ Real environment |
| Mouse/keyboard behavior | All automation without behavioral modeling | Very Hard | ❌ No | ✅ Human-like patterns |
| CDP runtime checks | Any CDP-based automation | Very Hard | ❌ No | ✅ No CDP exposure |
How to Test Your Browser Against Detection
Before deploying your automation, test it against these detection surfaces. Several tools exist that you can use to evaluate how your setup performs. Check out our roundup of browser fingerprint test tools for a complete list, but here are the essentials:
- CreepJS (abrahamjuliot.github.io/creepjs) — The most comprehensive open-source fingerprint analyzer. Tests navigator properties, canvas, WebGL, fonts, screen, and dozens more signals.
- BotD by Fingerprint.com — Specifically designed to detect automation and headless browsers. Tests for webdriver flags, CDP artifacts, and process-level signals.
- Pixelscan — Tests browser fingerprint consistency and flags mismatches between declared and actual browser properties.
- Browserleaks.com — Tests individual fingerprint vectors (canvas, WebGL, fonts, WebRTC, geolocation) in isolation.
How Send.win Helps You Master Headless Browser Detection Methods
Send.win makes Headless Browser Detection Methods simple and secure with powerful browser isolation technology:
- Browser Isolation – Every tab runs in a sandboxed environment
- Cloud Sync – Access your sessions from any device
- Multi-Account Management – Manage unlimited accounts safely
- No Installation Required – Works instantly in your browser
- Affordable Pricing – Enterprise features without enterprise costs
Try Send.win Free – No Credit Card Required
Experience the power of browser isolation with our free demo:
- Instant Access – Start testing in seconds
- Full Features – Try all capabilities
- Secure – Bank-level encryption
- Cross-Platform – Works on desktop, mobile, tablet
- 14-Day Money-Back Guarantee
Ready to upgrade? View pricing plans starting at just $9/month.
Run your automated browser through all of these before relying on it for production workloads. Pay particular attention to consistency — do the WebGL renderer, User-Agent, platform, and screen resolution all tell the same story?
Why Cloud Antidetect Browsers Eliminate All Detection Vectors
The fundamental problem with local stealth approaches is that they’re patching symptoms rather than solving root causes. A headless browser on your laptop is inherently different from a real user’s browser — no amount of JavaScript patching changes the underlying TLS stack, GPU hardware, or execution environment.
Cloud-based antidetect browsers like Send.win solve this by running real, headed browser instances on cloud infrastructure with:
- Real GPU rendering — No SwiftShader, no software fallback. Canvas and WebGL fingerprints come from actual hardware.
- Native TLS stacks — JA3/JA4 fingerprints match genuine Chrome installations because the browser IS a genuine Chrome installation.
- Isolated profiles — Each browser profile has its own cookies, storage, fingerprint configuration, and proxy binding. No cross-contamination between sessions.
- No CDP exposure — Automation commands are translated through the platform’s API layer, never exposing raw CDP to the page context.
- Headful rendering — Every session renders in a real display environment, eliminating all headless-specific detection vectors.
- Consistent fingerprints — Each profile maintains the same fingerprint across sessions, building reputation over time rather than appearing as a new device every run.
For teams using automation in scenarios like anti-bot detection bypass, this infrastructure-level approach is the only one that reliably passes all detection layers simultaneously.
🏆 Send.win Verdict
This guide covered every major headless browser detection method used by anti-bot systems in 2026 — from basic navigator.webdriver checks to advanced CDP artifact analysis, timing attacks, and behavioral biometrics. The takeaway is clear: stealth plugins can patch JavaScript-level checks, but they can’t change your GPU hardware, TLS fingerprint, or execution environment. Send.win eliminates every detection vector by running real, headed browser sessions on cloud infrastructure with genuine hardware fingerprints, native TLS stacks, and zero CDP exposure. No patches to maintain, no artifacts to leak.
Try Send.win free today — pass every headless detection test with real browser sessions that anti-bot systems can’t distinguish from human users.
Frequently Asked Questions
What are the most common headless browser detection methods?
The most common methods include checking the navigator.webdriver flag, analyzing WebGL renderer strings (looking for “Google SwiftShader”), examining the plugins and languages arrays, canvas fingerprinting, timing analysis using requestAnimationFrame and performance.now(), CDP artifact detection, and behavioral analysis of mouse movements and keyboard inputs.
Can Chrome’s new headless mode bypass all detection?
Chrome’s new headless mode (launched with --headless=new) is significantly harder to detect than the old headless mode, as it uses the same rendering pipeline as headed Chrome. However, it still lacks GPU acceleration by default (using SwiftShader), has subtle differences in requestAnimationFrame timing, and cannot fully emulate mouse/keyboard behavioral patterns. Advanced anti-bot systems can still detect it.
How does canvas fingerprinting detect headless browsers?
Canvas fingerprinting renders specific graphics (text with particular fonts, gradients, shapes) and hashes the pixel output. Because headless browsers use software rendering instead of GPU-accelerated rendering, the output pixels differ from those produced by a real browser with a real GPU. Even the same browser version on the same OS produces different canvas hashes depending on whether hardware acceleration is active.
What is CDP artifact detection and why is it hard to evade?
CDP (Chrome DevTools Protocol) artifact detection checks for side effects created when automation frameworks communicate with the browser. These include modified error stack traces, non-native property descriptors on navigator objects, timing differences from script injection, and changes to console API behavior. It’s hard to evade because the artifacts are inherent to how CDP works — you can’t use CDP to control a browser without creating these side effects.
Do stealth plugins like puppeteer-extra-stealth fully prevent detection?
No. Stealth plugins address JavaScript-level detection vectors (navigator.webdriver, plugins, WebGL strings) but cannot modify TLS fingerprints, GPU hardware, execution timing characteristics, or behavioral patterns. They also create their own detectable artifacts — anti-bot vendors specifically test against these plugins and can identify their patching patterns. They’re a useful first layer but insufficient against enterprise-grade detection.
How do websites use timing attacks to detect automation?
Timing attacks measure how long specific operations take — requestAnimationFrame intervals, DOM manipulation speed, property access latency, and performance.now() precision. Headless browsers on powerful servers execute operations much faster than real laptops, and their frame timing doesn’t match the vsync-locked 60fps cadence of a real display. These intrinsic timing differences are extremely difficult to spoof because they’re tied to the underlying hardware and execution environment.
What is the difference between headless and headful browser detection?
Headless detection specifically targets browsers running without a visible display — checking for software renderers, absent GPU acceleration, abnormal frame timing, and missing display-dependent APIs. Headful detection (or general automation detection) targets any automated browser, including those with visible windows, by checking for CDP artifacts, webdriver flags, and behavioral anomalies. A browser can pass all headless detection checks but still be caught by general automation detection.
What is the most effective way to avoid headless browser detection in 2026?
The most effective approach in 2026 is using a cloud-based antidetect browser platform that runs real, headed browser sessions with genuine hardware fingerprints. This eliminates all headless-specific detection vectors (no software renderer, real GPU output, proper frame timing) while also addressing automation detection vectors through isolated browser profiles, native TLS signatures, and behavioral pattern masking. Platforms like Send.win provide this as a managed service, removing the maintenance burden of keeping local stealth tools updated.
