Guides

How to detect headless browsers and automation on your signup page

The browser signals that give away Puppeteer, Playwright and Selenium signups, why each one can be faked on its own, and how to combine them without blocking real people.

9 min read · Updated 2026-10-31 · Attrelix

Why bots use real browsers now

A few years ago most signup bots were plain HTTP scripts posting your form directly. Those are easy to stop: they don't run JavaScript, so anything your page computes in the browser never arrives. Today most serious bots drive a real browser through Puppeteer, Playwright or Selenium, often headless, sometimes through an anti-detect browser that disguises its fingerprint. They run your JavaScript, render your page and fill in your form like a person would.

Detecting them means looking for the places where an automated browser differs from one a human is using. None of the signals below is proof on its own — each can be patched by a determined operator — but they are cheap to collect and they stack.

Signal 1: the WebDriver flag

Browsers under WebDriver-style automation set navigator.webdriver to true. That is a deliberate part of the WebDriver standard, and default setups of Selenium, Puppeteer and Playwright expose it.

const automated = navigator.webdriver === true;

“Stealth” plugins delete or override it, so its absence proves nothing. Its presence, though, is close to conclusive: real users essentially never have it.

Signal 2: the headless user agent

Headless Chrome identifies itself as HeadlessChrome in its user-agent string unless the operator overrides it. Check it on the server too, from the request header, since the agent string the browser reports in JavaScript can differ from what it sends.

Signal 3: a DevTools-protocol client attached

Puppeteer and Playwright control Chrome through the DevTools protocol. When a protocol client listens to console events, logging an object makes the browser serialize it — including reading properties a normal page would never touch. A getter that records whether it was read is a well-known way to notice this:

function devtoolsProtocolAttached() {
  let read = false;
  const e = new Error();
  Object.defineProperty(e, "stack", { get() { read = true; return ""; } });
  console.debug(e);
  return read;
}

It also fires when a developer has DevTools open, so give it moderate weight rather than treating it as a verdict.

Signal 4: software rendering and odd windows

Headless browsers on servers have no GPU, so WebGL falls back to a software renderer such as SwiftShader or llvmpipe. You can read the renderer name through the WEBGL_debug_renderer_info extension where the browser exposes it. Some headless setups also report a window of zero width or height. Virtual machines and remote desktops share these traits, so they are weak signals on their own.

Signal 5: a fingerprint that contradicts itself

Anti-detect browsers exist to look like many different real browsers. They do it by overriding properties, and overrides are rarely perfectly consistent. Things worth comparing:

  • The operating system in the user agent versus navigator.platform and User-Agent Client Hints.
  • The browser family in the user agent versus features only that engine has.
  • The installed fonts versus the fonts that operating system ships with.
  • A canvas drawn twice in a row producing two different images — a sign of injected noise.
  • Notification.permission saying “denied” while the Permissions API says “prompt”.

Each contradiction is weak alone, because privacy extensions and unusual setups produce some of them too. Two or three together is a strong pattern.

Signal 6: the token that never came back

A simple and robust technique: have the signup page's script request a short-lived, single-use token and put it in a hidden form field, then verify it on the server. A bot that skips your JavaScript arrives without a valid token; one that replays an old token arrives with a used one. You don't need to know how the bot works to notice it never ran your page properly. Keep the token single-use, bind it to your site, and let it expire in minutes.

Signal 7: behaviour, not fingerprints

Fingerprints can be faked; behaviour is harder. A person reads the form, moves the pointer and types. A script fills the fields and submits within a few hundred milliseconds of the page loading, with no trusted input events at all.

The reliable measurement needs no extra software: your agent issues the visitor token when the page loads, and your server compares that timestamp with the moment of the check. Under about a second from load to signup is a script. When the agent can also report trusted input events, an instant submit with zero pointer, key or touch events is a second, independent confirmation.

A caveat on signal 3: in current Chrome the DevTools trick only fires when the automation client subscribes to console events, so treat it as a bonus rather than the main detector.

Combine, don't block on any single signal

Treat each signal as points in a score and decide on the total. A reasonable starting weighting, from strongest to weakest:

SignalWeightWhy
navigator.webdriver truehighAlmost never true for real users
HeadlessChrome user agenthighOnly headless builds send it
Token missing or replayedhighThe page's script never ran, or ran once for many signups
DevTools protocol attachedmediumDevelopers with DevTools open trigger it too
OS or browser contradictionsmediumAnti-detect overrides, but also some extensions
Software renderer, zero windowlowVMs and remote desktops share them

Pair the browser signals with server-side ones — signups from cloud IP ranges, bursts from one subnet, accounts linked to the same device — and very few bots will look clean on all of them at once.

Things not to do

  • Don't block on the absence of plugins or languages alone. Privacy-hardened browsers strip them.
  • Don't ship your detection logic as obvious named checks that an operator can grep for and patch in one go; collect raw facts in the browser and decide on the server.
  • Don't trust anything the browser reports blindly. The client is under the bot's control. Verification happens on your server, with your secret.

How Attrelix does it

The Attrelix agent collects these browser facts and a single-use visitor token on your signup page; the server combines them with network, inbox and linked-account signals into one score. All the weights are public in the docs, and no single soft signal can push a signup to review by itself.

Summary

  • Modern bots run real browsers; look for where automation differs from a human.
  • WebDriver, headless UA and missing tokens are strong; the rest are weak but stack.
  • Decide on the server, from a score, never from one browser-side flag.

More guides