Seatext library / BotRefund evidence
How to Make a Headless Browser Undetectable: Step‑by‑Step Guide
Use stealth plugins, adjust browser fingerprints, and emulate real‑world behavior to hide a headless browser. Follow the ordered steps below and verify the result with a detection audit.
✓ Built for advertisers who need clear, refund-ready traffic evidence.
You can make a headless browser harder to detect by masking the signals that BotRefund and similar services check, such as the CDP debugger leak, user‑agent mismatches, and engine inconsistencies.
How headless detection works: the 106‑signal model
BotRefund looks at 106 browser, network, hardware, and behavior signals. No single signal decides; the AI weighs the full pattern before labeling traffic as human or bot.
When several signals appear together, the confidence of automation rises. This section explains the most relevant signals for headless browsers and how stealth fixes address each one.
WebRTC Network Leak
Why it exists: WebRTC can reveal local IP addresses even when a VPN is used.
Real browser: Returns the device’s local network interfaces via RTCPeerConnection.
Stealth fix: Block RTCPeerConnection or replace its IP with a fake one using puppeteer-extra-plugin-stealth or a custom page.evaluate.
DNS Tunnel Leak
Why it exists: DNS queries may go to a different resolver than HTTP traffic, exposing a mismatch.
Real browser: Uses the system DNS resolver for both DNS and HTTP requests.
Stealth fix: Route all traffic through the same proxy; ensure DNS settings match the HTTP proxy.
DNS Routing Mismatch
Why it exists: Some resolvers return different IPs for the same domain based on query type.
Real browser: Gets consistent A/AAAA records for a domain.
Stealth fix: Use a trusted resolver (e.g., Google 8.8.8.8) and disable custom DNS settings.
Latency Mismatch
Why it exists: Network round‑trip time reported by JavaScript may differ from actual TCP handshake.
Real browser: Measures latency consistently with network layer.
Stealth fix: Avoid aggressive throttling; keep network conditions natural.
Languages Mismatch
Why it exists: The navigator.languages list may not match the Accept‑Language header or IP location.
Real browser: Sends language preferences that align with geo‑IP.
Stealth fix: Set --lang and override navigator.languages to match the proxy’s locale.
HTTP User-Agent Mismatch
Why it exists: The User‑Agent header and navigator.userAgent can diverge.
Real browser: Header and object reflect the same Chrome version.
Stealth fix: Pass the user‑agent via launch args and overwrite navigator.userAgent in page.
CDP Debugger Leak
Why it exists: Automation leaves traces in the Chrome DevTools Protocol.
Real browser: No debugger agent attached unless devtools are open.
Stealth fix: Use puppeteer-extra-plugin-stealth to hide the debugger endpoint.
Engine Mismatch
Why it exists: The reported JavaScript engine version may differ from the actual Chrome build.
Real browser: engine property matches the binary.
Stealth fix: Patch navigator.userAgent, navigator.appVersion, and window.chrome to reflect a real build.
Automation Properties
Why it exists: Flags like navigator.webdriver are set by automation tools.
Real browser: These properties are undefined or false.
Stealth fix: Set navigator.webdriver = false and overwrite other automation flags.
What headless detection looks for
Bot detection platforms examine over a hundred signals across network, hardware, and browser layers. When several of these signals appear together, they flag the session as automated.
| Signal | What it checks | Stealth fix |
|---|---|---|
| CDP Debugger Leak | Traces left by browser automation or masking tools | Use puppeteer-extra-plugin-stealth to hide the debugger protocol |
| HTTP User-Agent Mismatch | Inconsistent user‑agent string between request headers and navigator object | Set a genuine user‑agent via args and overwrite navigator.userAgent |
| Engine Mismatch | Differences between reported JavaScript engine and real Chrome version | Patch navigator.webdriver, define window.chrome, and spoof navigator.appVersion |
| Timezone Bias | Location and language settings that don’t align | Match Intl.DateTimeFormat().resolvedOptions().timeZone to the IP location; set --lang=en-US |
| WebRTC Network Leak | Exposes local IP addresses through RTCPeerConnection | Block RTCPeerConnection or replace its IP with a fake value |
| DNS Tunnel Leak | DNS and web traffic follow different routes | Route all traffic through the same proxy; ensure DNS settings match the HTTP proxy |
| DNS Routing Mismatch | Inconsistent DNS responses for the same domain | Use a stable public resolver (e.g., 8.8.8.8) and disable custom DNS |
| Latency Mismatch | JS‑measured latency differs from actual network delay | Avoid extreme network throttling; keep connection characteristics natural |
| Languages Mismatch | navigator.languages does not match Accept‑Language or IP locale | Set --lang and override navigator.languages to match proxy locale |
Prerequisites before you start
- Node.js (or Python) environment with
puppeteerorseleniuminstalled. - Access to
puppeteer-extra-plugin-stealth(or equivalent for Selenium). - A real‑world user‑agent string from a recent Chrome version.
- Optionally, a VPN or residential proxy that matches the chosen timezone.
Step‑by‑step process to hide a headless browser
- Install the stealth plugin. For Puppeteer run
npm i puppeteer-extra puppeteer-extra-plugin-stealthand add it to your launch script. - Launch Chrome with realistic flags. Use
--no-sandbox,--disable-blink-features=AutomationControlled, and avoid--headlessif possible; instead useheadless: 'new'(Chrome 109+). The new headless mode reduces many fingerprint gaps compared to the legacy headless. - Override navigator properties. Inject JavaScript that sets
navigator.webdriver = false, defineswindow.chromewith typical properties, and alignslanguagesandpluginswith a real browser. - Synchronize time‑zone and locale. Set
--lang=en-USand adjustIntl.DateTimeFormat().resolvedOptions().timeZoneto match the proxy’s IP. - Patch the User‑Agent. Pass the chosen user‑agent via
args(e.g.,--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36) and also rewritenavigator.userAgentinside the page. - Disable WebRTC leaks. Add
--disable-features=WebRtcHideLocalIpsWithMdnsor use a page.evaluate to replaceRTCPeerConnectionwith a mock that returns empty ICE candidates. - Run a short test page. Load a page that prints the above properties; compare the output with a real Chrome session. Expected output shows
navigator.webdriver: false, matching user‑agent, and no WebRTC IP leaks. - Common pitfalls. Forgetting to overwrite
navigator.userAgentafter setting the args leaves a header/object mismatch. Using the old--headlessflag can expose theHeadlessChromestring in the user‑agent. Over‑blocking WebRTC (e.g., returning no IP at all) can itself become a signal because real browsers always expose some interface.
Common mistake to avoid
Changing only the user‑agent while leaving navigator.webdriver true is a red flag. Detection tools like BotRefund still see the automation flag and will label the session as a bot.
How to verify your browser is stealthy
After the steps, run BotRefund’s free audit (or any similar detection service). If the audit reports no “CDP Debugger Leak”, “Engine Mismatch”, or “User‑Agent Mismatch”, your setup is passing the most common checks.
Limitations and when stealth may still fail
Even with perfect fingerprint masking, advanced behavioral analysis—such as mouse‑movement jitter, click timing, and network latency patterns—can still reveal automation. If you need to hide those, consider adding human‑like interaction scripts or using a real device farm.
Practical trade‑offs and failure cases beyond fingerprinting
Stealth plugins hide static fingerprints but do not mimic human behavior. Bots that move the pointer in perfectly straight lines, click at exact millisecond intervals, or have uniform session lengths stand out.
Why it matters: Behavior signals like mouse‑jitter, pointer paths, click timing, session duration, and page engagement are part of the 106‑signal model. When these deviate from human norms, the AI raises the bot probability.
When stealth alone is insufficient: If your script performs repetitive actions without variance, detection systems flag the session despite a clean fingerprint.
Additional measures: Introduce random delays between actions, simulate realistic mouse trajectories with slight jitter, vary scroll depth, and mix page visits with idle time. Libraries such as puppeteer‑extra‑plugin‑human‑delay or selenium‑based action chains can help.
Trade‑offs: Adding behavioral noise slows down the script and may reduce throughput. Using a residential proxy improves IP reputation but adds cost and latency. Blocking WebRTC fully can leak the fact that you are hiding it; a better approach is to spoof the local IP to match the proxy’s address.
Bottom line: Fingerprint stealth is necessary but not sufficient. Combine it with realistic behavior, appropriate proxy selection, and continuous audit feedback to lower detection risk.
Further reading and comparison sources
These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.
Learn more
Visit the website for more information.