CAPTCHAs are often embedded inside one or more iframes. The CAPTCHA widget might sit inside a payment iframe, which itself is inside a modal iframe. To solve these CAPTCHAs, you need to navigate the iframe tree, extract the sitekey, solve via CaptchaAI, and inject the token back into the correct frame context.
How iframe CAPTCHAs work
A typical nested structure:
Main page
└── iframe#payment-frame (cross-origin)
└── iframe[src*="recaptcha/api2/anchor"] (Google-hosted)
└── reCAPTCHA widget
The sitekey lives in the outer iframe (the one that renders the widget). The innermost Google iframe is the visual challenge — you don't interact with it directly. The data-sitekey attribute or the k= parameter in the iframe src is what you need.
Python: Selenium iframe switching
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import re
import time
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example.com/checkout")
# Step 1: Switch into the outer iframe
wait = WebDriverWait(driver, 15)
outer_iframe = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "iframe#payment-frame"))
)
driver.switch_to.frame(outer_iframe)
# Step 2: Find the reCAPTCHA iframe and extract sitekey
recaptcha_iframe = wait.until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, 'iframe[src*="recaptcha/api2/anchor"]')
)
)
src = recaptcha_iframe.get_attribute("src")
sitekey = re.search(r"k=([A-Za-z0-9_-]+)", src).group(1)
page_url = driver.current_url
print(f"Sitekey: {sitekey}")
print(f"Page URL: {page_url}")
# Step 3: Solve with CaptchaAI
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1"
})
task_id = resp.json()["request"]
token = None
for _ in range(24):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if poll["status"] == 1:
token = poll["request"]
break
if poll["request"] != "CAPCHA_NOT_READY":
raise Exception(poll["request"])
print(f"Token: {token[:50]}...")
# Step 4: Inject token (still inside the outer iframe)
driver.execute_script("""
document.querySelector('textarea[name="g-recaptcha-response"]').value = arguments[0];
if (typeof grecaptcha !== 'undefined') {
grecaptcha.getResponse = function() { return arguments[0]; };
}
""", token)
# Step 5: Switch back to main page
driver.switch_to.default_content()
print("Token injected, switched back to main frame")
Expected output:
Sitekey: 6Le-SITEKEY-abc123
Page URL: https://example.com/checkout
Token: 03AGdBq26ZfPxL...
Token injected, switched back to main frame
JavaScript: Puppeteer frame handling
Puppeteer has first-class frame support — no manual switching needed.
const puppeteer = require('puppeteer');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com/checkout', {
waitUntil: 'networkidle2'
});
// Step 1: Find the payment iframe
const paymentFrameHandle = await page.waitForSelector('iframe#payment-frame');
const paymentFrame = await paymentFrameHandle.contentFrame();
// Step 2: Find reCAPTCHA iframe inside the payment frame
const recaptchaFrameHandle = await paymentFrame.waitForSelector(
'iframe[src*="recaptcha/api2/anchor"]'
);
const src = await paymentFrame.evaluate(
el => el.getAttribute('src'), recaptchaFrameHandle
);
const sitekey = src.match(/k=([A-Za-z0-9_-]+)/)[1];
const pageUrl = page.url();
console.log(`Sitekey: ${sitekey}`);
// Step 3: Solve with CaptchaAI
const submitResp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: pageUrl,
json: 1
}
});
const taskId = submitResp.data.request;
let token = null;
for (let i = 0; i < 24; i++) {
await new Promise(r => setTimeout(r, 5000));
const pollResp = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 }
});
if (pollResp.data.status === 1) {
token = pollResp.data.request;
break;
}
if (pollResp.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(pollResp.data.request);
}
}
console.log(`Token: ${token.substring(0, 50)}...`);
// Step 4: Inject token into the payment frame
await paymentFrame.evaluate((tkn) => {
const textarea = document.querySelector(
'textarea[name="g-recaptcha-response"]'
);
textarea.value = tkn;
const callback = document.querySelector('.g-recaptcha')
?.getAttribute('data-callback');
if (callback && typeof window[callback] === 'function') {
window[callback](tkn);
}
}, token);
console.log('Token injected into payment iframe');
})();
Deeply nested iframes
For three or more nesting levels, chain the frame switches:
Selenium
# Main → iframe-1 → iframe-2 → CAPTCHA
driver.switch_to.frame(driver.find_element(By.ID, "iframe-1"))
driver.switch_to.frame(driver.find_element(By.ID, "iframe-2"))
# Now extract sitekey from this context
# To go back up one level:
driver.switch_to.parent_frame()
# To go back to main:
driver.switch_to.default_content()
Puppeteer
const frame1 = await (await page.$('iframe#iframe-1')).contentFrame();
const frame2 = await (await frame1.$('iframe#iframe-2')).contentFrame();
// Extract sitekey from frame2
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Using main page URL as pageurl |
Token rejected by site | Use the URL of the frame that renders the CAPTCHA widget |
| Not switching back to parent frame | Subsequent actions fail | Call switch_to.parent_frame() or switch_to.default_content() after injection |
| Extracting sitekey from wrong iframe | ERROR_WRONG_GOOGLEKEY |
The sitekey is on the iframe that contains .g-recaptcha, not the inner Google challenge iframe |
| Cross-origin iframe blocking JS | SecurityError in console |
You cannot execute_script inside a cross-origin iframe — extract sitekey from the src attribute instead |
FAQ
The iframe is cross-origin. Can I still extract the sitekey?
Yes. The sitekey is in the iframe element's src attribute or data-sitekey attribute, which are readable from the parent frame. You never need to execute JavaScript inside the cross-origin iframe.
Should I use the parent page URL or the iframe URL for pageurl?
Use the URL of the page that loads the reCAPTCHA widget. This is typically the iframe's own URL, not the top-level page. Check the site's reCAPTCHA configuration to confirm.
How do I know which frame to inject the token into?
Inject the token into the frame that contains the textarea[name="g-recaptcha-response"] element — this is always the frame that rendered the .g-recaptcha div.
Solve CAPTCHAs in any iframe depth with CaptchaAI
Get your API key at captchaai.com.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.