Explainers

reCAPTCHA Anchor and Bframe URL Extraction Guide

reCAPTCHA renders inside two nested iframes: the anchor iframe (containing the checkbox widget) and the bframe iframe (containing the image challenge). These iframes contain encoded parameters that some advanced automation workflows need to extract. This guide explains the iframe architecture, parameter format, and extraction techniques.


reCAPTCHA iframe architecture

Target page (example.com/login)
    └── <iframe src="https://www.google.com/recaptcha/api2/anchor?...">
        │   ← Anchor iframe: "I'm not a robot" checkbox
        │
        └── <iframe src="https://www.google.com/recaptcha/api2/bframe?...">
                ← Bframe iframe: Image challenge grid (loads when clicked)

Anchor iframe

The anchor iframe contains the checkbox widget and initial risk analysis:

https://www.google.com/recaptcha/api2/anchor?
    ar=1
    &k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp  ← site key
    &co=aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM.           ← encoded origin
    &hl=en                                           ← language
    &v=jF2Zb_rr_5sv8dMHoGIn-XxY                    ← reCAPTCHA version
    &size=normal                                     ← widget size
    &cb=89fu2pf0swif                                ← callback ID

Bframe iframe

The bframe iframe contains the image challenge (only loaded when the checkbox click triggers a challenge):

https://www.google.com/recaptcha/api2/bframe?
    hl=en
    &v=jF2Zb_rr_5sv8dMHoGIn-XxY
    &k=6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp

Anchor URL parameters

Parameter Name Description
k Site key The reCAPTCHA site key
co Encoded origin Base64-encoded origin (protocol + domain + port)
v Version reCAPTCHA JavaScript bundle version hash
hl Language Challenge language code
size Size normal, compact, or invisible
cb Callback Unique callback function identifier
theme Theme light or dark
ar Aspect ratio Display aspect ratio flag

Decoding the co parameter

The co parameter contains the base64-encoded origin:

import base64

co_value = "aHR0cHM6Ly9leGFtcGxlLmNvbTo0NDM."
# Remove trailing period (padding artifact)
decoded = base64.b64decode(co_value.rstrip(".") + "==").decode()
print(decoded)  # "https://example.com:443"

This reveals the origin domain the reCAPTCHA was configured for.


Extracting anchor and bframe URLs

Python extraction from page source

import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse, parse_qs
import re
import base64

def extract_recaptcha_iframes(url):
    """Extract reCAPTCHA anchor and bframe iframe URLs and parameters."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/120.0.0.0 Safari/537.36",
    }

    response = requests.get(url, headers=headers, timeout=15)
    soup = BeautifulSoup(response.text, "html.parser")

    result = {
        "anchor_url": None,
        "bframe_url": None,
        "site_key": None,
        "origin": None,
        "version": None,
        "language": None,
    }

    # Find anchor iframe
    anchor_iframe = soup.find("iframe", src=re.compile(r"recaptcha.*anchor"))
    if anchor_iframe:
        anchor_url = anchor_iframe.get("src", "")
        result["anchor_url"] = anchor_url

        # Parse parameters
        parsed = urlparse(anchor_url)
        params = parse_qs(parsed.query)

        result["site_key"] = params.get("k", [None])[0]
        result["version"] = params.get("v", [None])[0]
        result["language"] = params.get("hl", [None])[0]

        # Decode origin
        co = params.get("co", [None])[0]
        if co:
            try:
                padded = co.rstrip(".") + "=="
                result["origin"] = base64.b64decode(padded).decode()
            except Exception:
                result["origin"] = co

    # Find bframe iframe (may not be in source — loaded dynamically)
    bframe_iframe = soup.find("iframe", src=re.compile(r"recaptcha.*bframe"))
    if bframe_iframe:
        result["bframe_url"] = bframe_iframe.get("src", "")

    # Construct bframe URL from anchor parameters if not found
    if not result["bframe_url"] and result["site_key"] and result["version"]:
        result["bframe_url"] = (
            f"https://www.google.com/recaptcha/api2/bframe?"
            f"hl={result['language'] or 'en'}"
            f"&v={result['version']}"
            f"&k={result['site_key']}"
        )

    return result

iframes = extract_recaptcha_iframes("https://example.com/login")
print(f"Site key: {iframes['site_key']}")
print(f"Origin: {iframes['origin']}")
print(f"Anchor URL: {iframes['anchor_url']}")

Node.js extraction

const axios = require("axios");
const cheerio = require("cheerio");
const { URL } = require("url");

async function extractRecaptchaIframes(pageUrl) {
    const { data: html } = await axios.get(pageUrl, {
        headers: {
            "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
                "AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36",
        },
        timeout: 15000,
    });

    const $ = cheerio.load(html);
    const result = {
        anchorUrl: null,
        bframeUrl: null,
        siteKey: null,
        origin: null,
        version: null,
    };

    // Find anchor iframe
    const anchorIframe = $("iframe[src*='recaptcha'][src*='anchor']");
    if (anchorIframe.length) {
        const src = anchorIframe.attr("src");
        result.anchorUrl = src;

        const url = new URL(src);
        result.siteKey = url.searchParams.get("k");
        result.version = url.searchParams.get("v");

        // Decode origin
        const co = url.searchParams.get("co");
        if (co) {
            try {
                result.origin = Buffer.from(
                    co.replace(/\.$/, ""), "base64"
                ).toString();
            } catch {}
        }
    }

    // Construct bframe URL
    if (result.siteKey && result.version) {
        result.bframeUrl =
            `https://www.google.com/recaptcha/api2/bframe?` +
            `hl=en&v=${result.version}&k=${result.siteKey}`;
    }

    return result;
}

extractRecaptchaIframes("https://example.com/login").then(console.log);

Selenium extraction (dynamic pages)

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

def extract_iframes_selenium(url):
    """Extract reCAPTCHA iframe URLs from a dynamically loaded page."""
    driver = webdriver.Chrome()
    driver.get(url)
    time.sleep(3)  # Wait for reCAPTCHA to load

    result = {"anchor_url": None, "bframe_url": None}

    # Find all iframes
    iframes = driver.find_elements(By.TAG_NAME, "iframe")

    for iframe in iframes:
        src = iframe.get_attribute("src") or ""
        if "recaptcha" in src and "anchor" in src:
            result["anchor_url"] = src
        elif "recaptcha" in src and "bframe" in src:
            result["bframe_url"] = src

    driver.quit()
    return result

When you need anchor/bframe URLs

Most automation workflows do NOT need anchor or bframe URLs. The standard CaptchaAI API only requires sitekey and pageurl. However, anchor/bframe URLs are useful for:

1. Verifying the correct site key

When a page has multiple reCAPTCHA instances or the site key is dynamically loaded:

# Extract sitekey from anchor URL when it's not in the page HTML
iframes = extract_recaptcha_iframes(url)
sitekey = iframes["site_key"]  # Guaranteed correct from iframe URL

2. Determining the reCAPTCHA version

# The anchor URL reveals the exact reCAPTCHA version
if "/api2/anchor" in anchor_url:
    recaptcha_type = "v2"
elif "/enterprise/anchor" in anchor_url:
    recaptcha_type = "enterprise"

3. Matching the origin for domain-strict implementations

# Decode the origin from the co parameter
origin = decode_co_parameter(iframes["co"])
# Use this origin as the pageurl for the solver

4. Debugging solve failures

When tokens are rejected, comparing the anchor URL parameters with your solver request can reveal mismatches:

def debug_solve_params(anchor_url, solver_pageurl, solver_sitekey):
    """Compare anchor params with solver request to find mismatches."""
    parsed = urlparse(anchor_url)
    params = parse_qs(parsed.query)

    issues = []

    # Check sitekey
    anchor_key = params.get("k", [None])[0]
    if anchor_key != solver_sitekey:
        issues.append(f"Sitekey mismatch: anchor={anchor_key}, solver={solver_sitekey}")

    # Check origin
    co = params.get("co", [None])[0]
    if co:
        origin = base64.b64decode(co.rstrip(".") + "==").decode()
        solver_parsed = urlparse(solver_pageurl)
        solver_origin = f"{solver_parsed.scheme}://{solver_parsed.netloc}"
        if origin != solver_origin:
            issues.append(f"Origin mismatch: anchor={origin}, solver={solver_origin}")

    return issues if issues else ["No mismatches found"]

For most use cases, skip iframe extraction and solve directly with CaptchaAI:

import requests
import time

API_KEY = "YOUR_API_KEY"

# All you need: sitekey + pageurl
submit = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6LcR_RsTAAAAAN_r0GEkGBfq3L7KmU5JbPHJtwNp",
    "pageurl": "https://example.com/login",
    "json": 1,
})

task_id = submit.json()["request"]

for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY,
        "action": "get",
        "id": task_id,
        "json": 1,
    }).json()

    if result.get("status") == 1:
        token = result["request"]
        print(f"Token: {token[:50]}...")
        break

Anchor/bframe extraction is only needed when standard solving fails due to sitekey or domain issues that require deeper investigation.


Frequently asked questions

Do I need to provide anchor or bframe URLs to CaptchaAI?

No. CaptchaAI only needs the sitekey and pageurl. The solver handles anchor and bframe interaction internally. Extracting these URLs is useful for debugging but not required for solving.

Why does the bframe iframe not appear in the page source?

The bframe iframe is created dynamically by reCAPTCHA's JavaScript when the user clicks the checkbox and a challenge is triggered. It is not present in the initial HTML. You need Selenium or Puppeteer to interact with the widget and capture the bframe URL.

What is the v parameter in the anchor URL?

The v parameter is the reCAPTCHA JavaScript bundle version hash. It changes periodically when Google updates reCAPTCHA. It is not needed for solving — CaptchaAI handles version differences automatically.

Can the co parameter help debug domain issues?

Yes. The co parameter is the base64-encoded origin (protocol + domain + port). Decoding it reveals exactly which domain reCAPTCHA thinks it is running on. If this does not match the domain you are submitting the token to, you have found the domain mismatch causing token rejection.


Summary

reCAPTCHA uses two iframes: anchor (checkbox widget) and bframe (image challenge). The anchor URL contains the site key, encoded origin, and version hash. For standard solving with CaptchaAI, you only need the sitekey and pageurl — no iframe URL extraction required. Use iframe extraction techniques for debugging domain verification failures or extracting site keys from dynamically loaded pages.

Discussions (0)

No comments yet.