Use Cases

Headless Browser CAPTCHA Issues and Solutions

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.

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.