Reference

Chrome DevTools Protocol + CaptchaAI: Low-Level CAPTCHA Automation

Chrome DevTools Protocol (CDP) gives you direct control over Chrome at the protocol level — no WebDriver, no extra abstraction layers. For CAPTCHA automation, this means better stealth, lower overhead, and fine-grained control over network requests, page execution, and browser behavior.


Why CDP Over WebDriver?

Feature WebDriver CDP
Detection surface High (navigator.webdriver) Minimal
Network interception Requires Selenium Wire Built-in (Fetch.requestPaused)
JavaScript injection executeScript (detectable) Runtime.evaluate (stealth)
Performance overhead Medium-high Low
Protocol support HTTP + JSON Wire WebSocket (real-time)

Direct CDP Connection (Node.js)

Connect to Chrome

# Launch Chrome with remote debugging
chrome --remote-debugging-port=9222 --no-first-run --no-default-browser-check
const WebSocket = require("ws");
const http = require("http");

class CDPClient {
  constructor() {
    this.ws = null;
    this.id = 0;
    this.callbacks = new Map();
    this.eventHandlers = new Map();
  }

  async connect(port = 9222) {
    // Get WebSocket URL from Chrome
    const targets = await this.httpGet(
      `http://127.0.0.1:${port}/json/list`
    );
    const target = targets.find((t) => t.type === "page");

    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(target.webSocketDebuggerUrl);
      this.ws.on("open", () => resolve());
      this.ws.on("error", reject);
      this.ws.on("message", (data) => this.handleMessage(JSON.parse(data)));
    });
  }

  httpGet(url) {
    return new Promise((resolve, reject) => {
      http.get(url, (res) => {
        let body = "";
        res.on("data", (c) => (body += c));
        res.on("end", () => resolve(JSON.parse(body)));
      }).on("error", reject);
    });
  }

  handleMessage(msg) {
    if (msg.id && this.callbacks.has(msg.id)) {
      this.callbacks.get(msg.id)(msg);
      this.callbacks.delete(msg.id);
    }
    if (msg.method && this.eventHandlers.has(msg.method)) {
      for (const handler of this.eventHandlers.get(msg.method)) {
        handler(msg.params);
      }
    }
  }

  send(method, params = {}) {
    return new Promise((resolve) => {
      const id = ++this.id;
      this.callbacks.set(id, resolve);
      this.ws.send(JSON.stringify({ id, method, params }));
    });
  }

  on(method, handler) {
    if (!this.eventHandlers.has(method)) {
      this.eventHandlers.set(method, []);
    }
    this.eventHandlers.get(method).push(handler);
  }
}

CaptchaAI + CDP Integration

const https = require("https");

class CDPCaptchaSolver {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.cdp = new CDPClient();
    this.API = "https://ocr.captchaai.com";
  }

  async init(port = 9222) {
    await this.cdp.connect(port);

    // Enable required domains
    await this.cdp.send("Page.enable");
    await this.cdp.send("Runtime.enable");
    await this.cdp.send("Network.enable");
    await this.cdp.send("DOM.enable");
  }

  async navigate(url) {
    const result = await this.cdp.send("Page.navigate", { url });
    await this.waitForLoad();
    return result;
  }

  async waitForLoad() {
    return new Promise((resolve) => {
      this.cdp.on("Page.loadEventFired", () => resolve());
    });
  }

  async detectSitekey() {
    const result = await this.cdp.send("Runtime.evaluate", {
      expression: `
        (() => {
          // reCAPTCHA
          const recaptcha = document.querySelector('[data-sitekey]');
          if (recaptcha) {
            return {
              type: 'recaptcha_v2',
              sitekey: recaptcha.getAttribute('data-sitekey'),
            };
          }

          // Turnstile
          const turnstile = document.querySelector('.cf-turnstile[data-sitekey]');
          if (turnstile) {
            return {
              type: 'turnstile',
              sitekey: turnstile.getAttribute('data-sitekey'),
            };
          }

          // reCAPTCHA v3 (script-based)
          const scripts = document.querySelectorAll('script[src*="recaptcha"]');
          for (const s of scripts) {
            const match = s.src.match(/render=([\\w-]+)/);
            if (match && match[1] !== 'explicit') {
              return { type: 'recaptcha_v3', sitekey: match[1] };
            }
          }

          return null;
        })()
      `,
      returnByValue: true,
    });

    return result.result?.value || null;
  }

  async solveCaptcha(siteUrl, sitekey, type = "recaptcha_v2") {
    const submitData = {
      key: this.apiKey,
      pageurl: siteUrl,
      json: "1",
    };

    if (type === "turnstile") {
      submitData.method = "turnstile";
      submitData.sitekey = sitekey;
    } else {
      submitData.method = "userrecaptcha";
      submitData.googlekey = sitekey;
    }

    const submitResp = await this.httpPost(
      `${this.API}/in.php`,
      submitData
    );

    if (submitResp.status !== 1) {
      throw new Error(`Submit: ${submitResp.request}`);
    }

    const taskId = submitResp.request;

    for (let i = 0; i < 60; i++) {
      await this.sleep(5000);

      const params = new URLSearchParams({
        key: this.apiKey,
        action: "get",
        id: taskId,
        json: "1",
      });

      const result = await this.httpGet(
        `${this.API}/res.php?${params}`
      );

      if (result.request === "CAPCHA_NOT_READY") continue;
      if (result.status !== 1) throw new Error(`Solve: ${result.request}`);
      return result.request;
    }

    throw new Error("Timeout");
  }

  async injectToken(token, type = "recaptcha_v2") {
    if (type === "turnstile") {
      await this.cdp.send("Runtime.evaluate", {
        expression: `
          const input = document.querySelector('input[name="cf-turnstile-response"]');
          if (input) {
            input.value = '${token}';
            input.dispatchEvent(new Event('change', { bubbles: true }));
          }
        `,
      });
    } else {
      await this.cdp.send("Runtime.evaluate", {
        expression: `
          // Set response textarea
          const textarea = document.querySelector('#g-recaptcha-response');
          if (textarea) {
            textarea.style.display = 'block';
            textarea.value = '${token}';
          }

          // Set all hidden fields
          document.querySelectorAll('[name="g-recaptcha-response"]')
            .forEach(el => { el.value = '${token}'; });

          // Trigger callback
          if (typeof ___grecaptcha_cfg !== 'undefined') {
            const clients = ___grecaptcha_cfg.clients;
            for (const key in clients) {
              const client = clients[key];
              for (const prop in client) {
                const val = client[prop];
                if (val && typeof val === 'object') {
                  for (const p in val) {
                    if (typeof val[p]?.callback === 'function') {
                      val[p].callback('${token}');
                    }
                  }
                }
              }
            }
          }
        `,
      });
    }
  }

  // Full workflow
  async solveOnPage(url) {
    await this.navigate(url);
    await this.sleep(2000);

    const captcha = await this.detectSitekey();
    if (!captcha) {
      console.log("No CAPTCHA detected");
      return null;
    }

    console.log(`Detected: ${captcha.type} (${captcha.sitekey})`);
    const token = await this.solveCaptcha(url, captcha.sitekey, captcha.type);
    await this.injectToken(token, captcha.type);
    console.log("Token injected");
    return token;
  }

  // HTTP helpers
  httpPost(url, data) {
    return new Promise((resolve, reject) => {
      const params = new URLSearchParams(data).toString();
      const u = new URL(url);
      const req = https.request({
        hostname: u.hostname, path: u.pathname,
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
      }, (res) => {
        let body = "";
        res.on("data", (c) => (body += c));
        res.on("end", () => resolve(JSON.parse(body)));
      });
      req.on("error", reject);
      req.write(params);
      req.end();
    });
  }

  httpGet(url) {
    return new Promise((resolve, reject) => {
      https.get(url, (res) => {
        let body = "";
        res.on("data", (c) => (body += c));
        res.on("end", () => resolve(JSON.parse(body)));
      }).on("error", reject);
    });
  }

  sleep(ms) {
    return new Promise((r) => setTimeout(r, ms));
  }
}

Network Interception with CDP

async function interceptCaptchaRequests(solver) {
  // Enable Fetch domain for request interception
  await solver.cdp.send("Fetch.enable", {
    patterns: [
      { urlPattern: "*recaptcha*", requestStage: "Request" },
      { urlPattern: "*turnstile*", requestStage: "Request" },
      { urlPattern: "*challenges.cloudflare.com*", requestStage: "Request" },
    ],
  });

  solver.cdp.on("Fetch.requestPaused", async (params) => {
    const { requestId, request } = params;

    console.log(`Intercepted: ${request.method} ${request.url}`);

    // Log CAPTCHA-related requests for debugging
    if (request.url.includes("userverify") || request.url.includes("reload")) {
      console.log("CAPTCHA verification request detected");
    }

    // Continue the request
    await solver.cdp.send("Fetch.continueRequest", { requestId });
  });
}

Stealth-Configuredion with CDP

async function applyStealthPatches(solver) {
  // Remove webdriver flag
  await solver.cdp.send("Page.addScriptToEvaluateOnNewDocument", {
    source: `
      Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined,
      });

      // Fix chrome object
      window.chrome = { runtime: {}, loadTimes: () => ({}) };

      // Fix permissions
      const originalQuery = window.navigator.permissions.query;
      window.navigator.permissions.query = (params) => {
        if (params.name === 'notifications') {
          return Promise.resolve({ state: Notification.permission });
        }
        return originalQuery.call(navigator.permissions, params);
      };

      // Fix plugins
      Object.defineProperty(navigator, 'plugins', {
        get: () => [1, 2, 3, 4, 5],
      });

      // Fix languages
      Object.defineProperty(navigator, 'languages', {
        get: () => ['en-US', 'en'],
      });
    `,
  });
}

Python CDP Integration

import asyncio
import aiohttp
import json

class CDPCaptchaSolver:
    CAPTCHAAI_URL = "https://ocr.captchaai.com"

    def __init__(self, api_key, cdp_port=9222):
        self.api_key = api_key
        self.cdp_port = cdp_port
        self.ws = None
        self.msg_id = 0

    async def connect(self):
        async with aiohttp.ClientSession() as session:
            async with session.get(
                f"http://127.0.0.1:{self.cdp_port}/json/list"
            ) as resp:
                targets = await resp.json()

        target = next(t for t in targets if t["type"] == "page")
        self.ws = await asyncio.get_event_loop().create_connection(
            lambda: CDPProtocol(self),
            target["webSocketDebuggerUrl"],
        )

    async def send(self, method, params=None):
        self.msg_id += 1
        msg = {"id": self.msg_id, "method": method, "params": params or {}}
        self.ws.send(json.dumps(msg))
        # Wait for response (simplified)
        return await self._wait_response(self.msg_id)

    async def solve_and_inject(self, url):
        await self.send("Page.navigate", {"url": url})
        await asyncio.sleep(3)

        # Detect sitekey via Runtime.evaluate
        result = await self.send("Runtime.evaluate", {
            "expression": "document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')",
            "returnByValue": True,
        })

        sitekey = result.get("result", {}).get("value")
        if not sitekey:
            return None

        # Solve via CaptchaAI
        token = await self._solve_recaptcha(url, sitekey)

        # Inject
        await self.send("Runtime.evaluate", {
            "expression": f"""
                document.querySelector('#g-recaptcha-response').value = '{token}';
                document.querySelectorAll('[name="g-recaptcha-response"]')
                    .forEach(el => {{ el.value = '{token}'; }});
            """,
        })

        return token

    async def _solve_recaptcha(self, site_url, sitekey):
        import requests

        resp = requests.post(f"{self.CAPTCHAAI_URL}/in.php", data={
            "key": self.api_key, "method": "userrecaptcha",
            "googlekey": sitekey, "pageurl": site_url, "json": 1,
        })
        task_id = resp.json()["request"]

        for _ in range(60):
            await asyncio.sleep(5)
            resp = requests.get(f"{self.CAPTCHAAI_URL}/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            })
            data = resp.json()
            if data["request"] == "CAPCHA_NOT_READY":
                continue
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("CAPTCHA solve timeout")

Usage Example

// Full workflow
async function main() {
  const solver = new CDPCaptchaSolver("YOUR_API_KEY");
  await solver.init(9222);

  // Apply stealth
  await applyStealthPatches(solver);

  // Enable network monitoring
  await interceptCaptchaRequests(solver);

  // Solve CAPTCHA on target page
  const token = await solver.solveOnPage("https://example.com/login");

  if (token) {
    // Submit form
    await solver.cdp.send("Runtime.evaluate", {
      expression: `document.querySelector('form').submit()`,
    });
  }
}

main().catch(console.error);

Troubleshooting

Issue Cause Fix
Can't connect to Chrome Remote debugging not enabled Launch with --remote-debugging-port=9222
WebSocket closes Chrome crashed or tab closed Add reconnection logic
Runtime.evaluate fails Page not loaded Wait for Page.loadEventFired
CAPTCHA still detected Incomplete stealth patches Add User-Agent override, fix navigator.plugins
Token injection fails Shadow DOM contains CAPTCHA Use DOM.describeNode to traverse shadow roots

FAQ

Is CDP harder to use than WebDriver?

CDP is lower-level but more powerful. For CAPTCHA automation, the stealth advantages outweigh the complexity.

Can I use CDP with Puppeteer?

Yes — Puppeteer uses CDP under the hood. You can access the CDP session directly via page._client() or page.createCDPSession().

Does CDP work with Firefox?

Firefox has partial CDP support. For full CDP, use Chromium-based browsers.

Is CDP detection-proof?

CDP is harder to detect than WebDriver, but not invisible. Combine with fingerprint spoofing for best results.



Get protocol-level control over CAPTCHA automation — get your CaptchaAI key and integrate with Chrome DevTools Protocol.

Discussions (0)

No comments yet.