GeeTest v3 returns three values after solving: geetest_challenge, geetest_validate, and geetest_seccode. Injecting these correctly into a browser automation session requires understanding where the site expects them — hidden form fields, JavaScript callbacks, or XHR payloads. Here's how to do it in each major framework.
What You're Injecting
CaptchaAI returns a three-part result:
{
"geetest_challenge": "a1b2c3d4e5...modified_challenge",
"geetest_validate": "abc123def456_validate",
"geetest_seccode": "abc123def456_validate|jordan"
}
All three values must be injected. Missing any one causes verification failure.
Step 1: Extract Parameters and Solve
Common to all frameworks — extract gt and challenge, then solve with CaptchaAI:
import requests
import time
def solve_geetest(gt, challenge, page_url):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"] # Returns dict with three values
raise TimeoutError("GeeTest solve timed out")
Step 2: Inject in Playwright (Python)
Extract Parameters
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# Capture GeeTest registration response
geetest_data = {}
def capture_geetest(response):
if "register" in response.url and response.status == 200:
try:
data = response.json()
if "gt" in data and "challenge" in data:
geetest_data.update(data)
except Exception:
pass
page.on("response", capture_geetest)
page.goto("https://example.com/login")
page.wait_for_selector(".geetest_holder")
gt = geetest_data["gt"]
challenge = geetest_data["challenge"]
Inject Solution
# Solve with CaptchaAI
solution = solve_geetest(gt, challenge, page.url)
# Method 1: Set hidden form fields
page.evaluate(f"""
const fields = {{
'geetest_challenge': '{solution["geetest_challenge"]}',
'geetest_validate': '{solution["geetest_validate"]}',
'geetest_seccode': '{solution["geetest_seccode"]}'
}};
for (const [name, value] of Object.entries(fields)) {{
let input = document.querySelector(`input[name="${{name}}"]`);
if (!input) {{
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
document.querySelector('form').appendChild(input);
}}
input.value = value;
}}
""")
# Submit the form
page.click("#submit-button")
Method 2: Trigger the GeeTest Callback
Some sites use GeeTest's JavaScript callback instead of form fields:
page.evaluate(f"""
// Find the GeeTest captcha object
if (window.captchaObj) {{
// Simulate a successful solve
const result = {{
geetest_challenge: '{solution["geetest_challenge"]}',
geetest_validate: '{solution["geetest_validate"]}',
geetest_seccode: '{solution["geetest_seccode"]}'
}};
// Override getValidate to return our solution
window.captchaObj.getValidate = function() {{ return result; }};
// Trigger the success callback
const successEvent = new Event('geetest_success');
document.dispatchEvent(successEvent);
}}
""")
Step 3: Inject in Puppeteer (JavaScript)
const puppeteer = require('puppeteer');
async function solveAndInject() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Capture GeeTest params
let gt, challenge;
page.on('response', async (response) => {
if (response.url().includes('register') && response.status() === 200) {
try {
const data = await response.json();
if (data.gt && data.challenge) {
gt = data.gt;
challenge = data.challenge;
}
} catch (e) {}
}
});
await page.goto('https://example.com/login');
await page.waitForSelector('.geetest_holder');
// Solve with CaptchaAI (implementation from earlier)
const solution = await solveCaptcha(gt, challenge, page.url());
// Inject the three values
await page.evaluate((sol) => {
// Set hidden inputs
const form = document.querySelector('form');
['geetest_challenge', 'geetest_validate', 'geetest_seccode'].forEach(name => {
let input = document.querySelector(`input[name="${name}"]`);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}
input.value = sol[name];
});
}, solution);
await page.click('#submit-button');
}
Step 4: Inject in Selenium (Python)
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
driver = webdriver.Chrome()
driver.get("https://example.com/login")
# Wait for GeeTest widget
WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CLASS_NAME, "geetest_holder"))
)
# Extract gt and challenge from the page
gt = driver.execute_script(
"return document.querySelector('[data-gt]')?.dataset.gt"
)
challenge = driver.execute_script(
"return document.querySelector('[data-challenge]')?.dataset.challenge"
)
# Solve with CaptchaAI
solution = solve_geetest(gt, challenge, driver.current_url)
# Inject via JavaScript
driver.execute_script(f"""
var fields = {{
'geetest_challenge': '{solution["geetest_challenge"]}',
'geetest_validate': '{solution["geetest_validate"]}',
'geetest_seccode': '{solution["geetest_seccode"]}'
}};
var form = document.querySelector('form');
for (var name in fields) {{
var input = document.querySelector('input[name="' + name + '"]');
if (!input) {{
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}}
input.value = fields[name];
}}
""")
driver.find_element(By.ID, "submit-button").click()
Handling XHR-Based Submissions
Some sites submit GeeTest results via XHR instead of form POST. Intercept and modify the request:
# Playwright: Intercept the XHR and inject values
def handle_route(route):
if "login" in route.request.url and route.request.method == "POST":
# Modify the POST data to include our solution
post_data = route.request.post_data
# Add GeeTest values to the request
route.continue_(post_data=modified_data)
else:
route.continue_()
page.route("**/api/login**", handle_route)
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| "GeeTest validation failed" | Missing one of the three values | Verify all three values are injected |
| Challenge expired before injection | Too long between extraction and solve | Extract and solve in quick succession |
| Form submits but values not included | Wrong field names or form selector | Inspect the actual form to find correct names |
| Callback not triggered | Site uses a custom callback name | Find the callback in the initGeetest options |
| Values injected but widget still shows | Widget state not updated | Trigger the success callback programmatically |
FAQ
Do I need to visually hide the GeeTest widget after injection?
No. The widget's visual state doesn't affect the form submission. As long as the three values are in the form data, the server validates them regardless of the widget's appearance.
Can I inject GeeTest values without a browser?
Yes, if the site accepts a direct HTTP POST with the three values. Intercept the form submission endpoint and send the values via requests or any HTTP client.
Why does GeeTest use three separate values instead of one token?
The three values serve different purposes: geetest_challenge tracks the session, geetest_validate proves the challenge was solved, and geetest_seccode provides a hash for tamper detection.
Related Articles
- Solve Geetest V3 Nodejs
- Solving Geetest V3 Nodejs Captchaai
- Mobile Browser Automation Captcha Solving
Next Steps
Inject GeeTest solutions in any framework — get your CaptchaAI API key and start solving.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.