Headless browsers trigger CAPTCHAs more often than regular browsers. Sites detect headless Chrome, Firefox, and WebKit through JavaScript fingerprinting and serve challenges to block automation. Here's how to handle each issue.
Why Headless Browsers Get More CAPTCHAs
| Detection Method | What Sites Check |
|---|---|
navigator.webdriver |
Set to true in headless mode |
| Window dimensions | Headless often uses 800x600 default |
| WebGL renderer | Headless returns "SwiftShader" |
| Chrome DevTools Protocol | CDP port open |
| Missing plugins | No PDF viewer, Flash, etc. |
| Permissions API | Different responses in headless |
| User-Agent string | "HeadlessChrome" substring |
When these signals combine, anti-bot systems (reCAPTCHA, Cloudflare, DataDome) assign lower trust scores and show CAPTCHAs.
Solution 1: Solve CAPTCHAs via API
Instead of trying to avoid CAPTCHAs entirely, solve them when they appear. CaptchaAI works with any headless browser because it solves CAPTCHAs server-side.
Selenium (Python)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import requests
import time
API_KEY = "YOUR_API_KEY"
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
driver.get("https://example.com/login")
# Check for CAPTCHA
recaptcha = driver.find_elements("class name", "g-recaptcha")
if recaptcha:
site_key = recaptcha[0].get_attribute("data-sitekey")
# Solve via CaptchaAI
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": site_key, "pageurl": driver.current_url
})
task_id = resp.text.split("|")[1]
while True:
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY": continue
token = result.text.split("|")[1]
break
# Inject token
driver.execute_script(
f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';"
)
driver.find_element("css selector", "form").submit()
Puppeteer (Node.js)
const puppeteer = require("puppeteer");
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.goto("https://example.com/login");
// Check for CAPTCHA
const siteKey = await page
.$eval(".g-recaptcha", (el) => el.getAttribute("data-sitekey"))
.catch(() => null);
if (siteKey) {
const submit = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: page.url(),
},
});
const taskId = submit.data.split("|")[1];
let token;
while (true) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId },
});
if (result.data === "CAPCHA_NOT_READY") continue;
token = result.data.split("|")[1];
break;
}
await page.evaluate((t) => {
document.getElementById("g-recaptcha-response").innerHTML = t;
}, token);
await page.click('button[type="submit"]');
}
Solution 2: Reduce CAPTCHA Frequency
While you can always solve CAPTCHAs via API, reducing how often they appear saves time and cost.
Patch navigator.webdriver
# Selenium
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
// Puppeteer
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, "webdriver", { get: () => false });
});
Set Realistic Window Size
# Selenium
driver.set_window_size(1920, 1080)
// Puppeteer
await page.setViewport({ width: 1920, height: 1080 });
Use Stealth Plugins
# Puppeteer
npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require("puppeteer-extra");
const StealthPlugin = require("puppeteer-extra-plugin-stealth");
puppeteer.use(StealthPlugin());
# Selenium
pip install undetected-chromedriver
import undetected_chromedriver as uc
driver = uc.Chrome(headless=True)
Solution 3: Cloudflare Challenge Handling
Cloudflare challenge pages require more than a token — you need the cf_clearance cookie:
# CaptchaAI handles full Cloudflare challenges
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "cloudflare_challenge",
"pageurl": "https://example.com",
"proxy": "http://user:pass@proxy:port",
"proxytype": "HTTP"
})
task_id = resp.text.split("|")[1]
# Result includes cf_clearance cookie and user_agent
# Use both to make subsequent requests
Common Issues by Browser
| Browser | Issue | Fix |
|---|---|---|
| Headless Chrome | navigator.webdriver = true |
Use --disable-blink-features flag |
| Puppeteer | Missing browser plugins | Use puppeteer-extra-plugin-stealth |
| Selenium | enable-automation switch |
excludeSwitches: ["enable-automation"] |
| Playwright | WebKit fingerprint | Use Chromium channel with stealth patches |
| All | Consistent viewport size | Set to 1920x1080 or randomize |
FAQ
Is headless mode always detected?
Not always. With proper stealth configuration, many sites won't flag your headless browser. But sophisticated anti-bot systems (Cloudflare, PerimeterX) can still detect headless mode through advanced fingerprinting.
Should I use headed mode instead?
Headed mode reduces detection but requires a display server (Xvfb on Linux). CaptchaAI's API approach works regardless of headed/headless mode, making it the more reliable solution.
Can I avoid CAPTCHAs entirely?
Not reliably. Even well-configured headless browsers eventually trigger CAPTCHAs at scale. Using CaptchaAI as a CAPTCHA handling layer ensures your automation continues when challenges appear.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.