GeeTest is a Chinese CAPTCHA provider whose slide puzzle is deployed on thousands of global sites — particularly in fintech, gaming, and e-commerce. GeeTest challenges are significantly more complex than standard text CAPTCHAs and require specialized solver support. This guide covers both GeeTest v3 and v4, including how they work, which solvers handle them, and working Python integration code.
What Is GeeTest?
GeeTest (极验) is a behavioral CAPTCHA system originating from Wuhan, China. It is one of the most widely deployed CAPTCHAs in Asia-Pacific and appears globally on sites using Chinese-origin infrastructure or security stacks.
GeeTest challenges are interactive: - v3 — Classic slide puzzle ("drag the slider to fit the puzzle piece"), click challenge ("click in order"), or icon challenge ("select the icons in the correct sequence") - v4 — Updated architecture with more challenge variants, improved behavioral analysis, and a different server-side validation flow
Where you'll encounter GeeTest: - Binance, OKX, and other crypto exchanges - Tencent, ByteDance, and related apps - Many Chinese-origin gaming and e-commerce sites - Authentication flows on sites using OWASP-aligned WAFs with GeeTest integration
GeeTest v3 — How It Works
Technical Flow
- The page loads GeeTest's JS (
gt.js) and callsinitGeetest()with a gt key (the static product key for the integration) and a challenge (a dynamic, single-use token fetched from the site's own server at page load). - The user completes the interactive challenge.
- GeeTest JS returns three values:
geetest_challenge,geetest_validate, andgeetest_seccode. - These three values are submitted to the site's server, which verifies them with GeeTest's API.
Critical distinction: The challenge value is dynamic — it changes on every page load and fetched from the site's backend. The solver API requires both the static gt key and the current challenge value to solve.
Extracting v3 Parameters
import re
import requests
def get_geetest_v3_params(page_url: str) -> dict:
"""
Extract GeeTest v3 gt key and challenge from the page.
Note: challenge is often fetched dynamically via XHR — may need
to intercept the network request instead.
"""
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
html = requests.get(page_url, headers=headers).text
gt = re.search(r'"gt"\s*:\s*"([0-9a-f]{32})"', html)
challenge = re.search(r'"challenge"\s*:\s*"([0-9a-f]{32}(?:\.[0-9a-f]+)?)"', html)
if not gt or not challenge:
raise ValueError(
"GeeTest v3 parameters not found in HTML. "
"The challenge may be fetched via XHR — intercept /captcha/register or similar."
)
return {"gt": gt.group(1), "challenge": challenge.group(1)}
For sites that fetch the challenge via XHR, use Playwright to intercept the network request:
from playwright.sync_api import sync_playwright
def get_geetest_v3_via_xhr(page_url: str) -> dict:
"""Intercept the GeeTest challenge XHR to capture gt and challenge."""
captured = {}
def handle_response(response):
if "captcha/register" in response.url or "gt=" in response.url:
try:
data = response.json()
if "gt" in data:
captured["gt"] = data["gt"]
captured["challenge"] = data["challenge"]
except Exception:
pass
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.on("response", handle_response)
page.goto(page_url, wait_until="networkidle")
browser.close()
if not captured:
raise ValueError("Could not capture GeeTest v3 challenge via XHR")
return captured
GeeTest v4 — How It Works
GeeTest v4 (released ~2021) uses a different architecture:
- No separate
gt+challengepair. Instead, it uses a captcha_id (static) and initializes via a POST to GeeTest's API with the session context. - The output fields are different:
captcha_id,lot_number,pass_token,gen_time,captcha_output. - Behavioral scoring is more aggressive — faster solves are penalized more heavily.
v4 is identifiable by new Gt4({captchaId: "..."}) or initGeetest4() in the page JavaScript.
Solver Support for GeeTest
| Solver | v3 Support | v4 Support | Success Rate | Avg Solve Time | Notes |
|---|---|---|---|---|---|
| CaptchaAI | ✅ | ✅ | ~94–96% | 10–20s | Best combined v3+v4 accuracy per benchmark |
| 2Captcha | ✅ | ✅ | ~89–93% | 15–30s | Established; both versions supported |
| Anti-Captcha | ✅ | ✅ | ~88–93% | 15–25s | Reliable at scale |
| CapSolver | ✅ | ✅ | ~92–95% | 12–22s | Strong automation-grade speed |
| CapMonster Cloud | ✅ | ✅ | ~86–91% | 15–25s | Affordable; v4 support added recently |
| NopeCHA | ❌ | ❌ | N/A | N/A | No GeeTest support |
| DeathByCaptcha | ✅ | ❌ | ~80–86% | 25–40s | v3 only; not recommended at volume |
GeeTest success rates are more sensitive to IP reputation than reCAPTCHA or Turnstile. Residential proxies improve v3 and v4 solve rates significantly.
How to Solve GeeTest v3 in Python
import requests
import time
def solve_geetest_v3(
api_key: str,
page_url: str,
gt: str,
challenge: str,
) -> dict:
"""
Solve GeeTest v3 using CaptchaAI (2Captcha-compatible API).
Returns dict with geetest_challenge, geetest_validate, geetest_seccode.
"""
payload = {
"key": api_key,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": page_url,
"json": 1,
}
r = requests.post("https://ocr.captchaai.com/in.php", data=payload, timeout=30)
r.raise_for_status()
result = r.json()
if result.get("status") != 1:
raise RuntimeError(f"Submit failed: {result}")
task_id = result["request"]
time.sleep(10)
for _ in range(24):
r = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": api_key, "action": "get", "id": task_id, "json": 1},
timeout=30,
)
data = r.json()
if data.get("status") == 1:
# Returns pipe-delimited: challenge|validate|seccode
parts = data["request"].split("|")
return {
"geetest_challenge": parts[0],
"geetest_validate": parts[1],
"geetest_seccode": parts[2],
}
time.sleep(5)
raise TimeoutError("GeeTest v3 solve timed out")
Submitting the v3 Solution
import json
def submit_geetest_v3_solution(
form_endpoint: str,
solution: dict,
other_fields: dict,
) -> requests.Response:
"""Submit form with GeeTest v3 solution."""
payload = {**other_fields, **solution}
r = requests.post(form_endpoint, json=payload, timeout=30)
return r
How to Solve GeeTest v4 in Python
def solve_geetest_v4(
api_key: str,
page_url: str,
captcha_id: str,
) -> dict:
"""
Solve GeeTest v4 using CaptchaAI.
Returns dict with captcha_id, lot_number, pass_token, gen_time, captcha_output.
"""
payload = {
"key": api_key,
"method": "geetest4",
"captcha_id": captcha_id,
"pageurl": page_url,
"json": 1,
}
r = requests.post("https://ocr.captchaai.com/in.php", data=payload, timeout=30)
r.raise_for_status()
result = r.json()
if result.get("status") != 1:
raise RuntimeError(f"Submit failed: {result}")
task_id = result["request"]
time.sleep(10)
for _ in range(30):
r = requests.get(
"https://ocr.captchaai.com/res.php",
params={"key": api_key, "action": "get", "id": task_id, "json": 1},
timeout=30,
)
data = r.json()
if data.get("status") == 1:
# v4 returns JSON string
return json.loads(data["request"])
time.sleep(5)
raise TimeoutError("GeeTest v4 solve timed out")
Common Errors and Fixes
ERROR_CAPTCHA_UNSOLVABLE on v3
The challenge value was stale when submitted to the solver. The challenge parameter must be the current session's challenge — it is valid only once and only for the current page load. Re-fetch it immediately before calling the solver.
"Invalid challenge" on server submission (v3)
The geetest_challenge in the solution must match the original challenge. If the solver returns a different challenge value in its solution (some implementations do), use the solution's geetest_challenge, not the original.
v4 "lot_number invalid"
The captcha_id was incorrect. Inspect the initGeetest4() call or the network request to /api/v1/register to find the correct static ID.
Low success rate (< 85%) on v3 GeeTest v3 performs behavioral analysis on the solve pattern. Flagged datacenter IPs are assigned harder challenges. Use a residential proxy for the page session (not the solver API call).
"Server verification failed" despite valid-looking solution GeeTest v3 server verification is time-sensitive. The site's backend must verify the solution within a few seconds of receiving it. Avoid buffering the solution before submission.
GeeTest v3 vs v4 — Key Differences
| Dimension | GeeTest v3 | GeeTest v4 |
|---|---|---|
| Init parameters | gt (static) + challenge (dynamic) |
captcha_id (static) |
| Output fields | challenge, validate, seccode |
lot_number, pass_token, gen_time, captcha_output |
| Challenge fetch | Page HTML or XHR | API POST to GeeTest |
| Behavioral scoring | Moderate | Aggressive |
| Solver method param | geetest |
geetest4 |
When to Use Which Solver
| Scenario | Recommended Solver | Reason |
|---|---|---|
| GeeTest v3 or v4, general automation | CaptchaAI | Best combined accuracy per CaptchaRank benchmark |
| High-volume GeeTest v3 | 2Captcha or CapSolver | Deep worker pools at scale |
| Cost-sensitive v3 | CapMonster Cloud | Competitive pricing |
| v4 only | CapSolver | Strong v4-specific implementation |
| Legacy pipeline | 2Captcha | No integration changes needed |
Explore More in This Hub
- Best GeeTest CAPTCHA Solver — ranked comparison with pricing and v3/v4 notes
- CAPTCHA Solver API Integration Guide — language-agnostic patterns
- CAPTCHA Solving in Python: Quick Start — full setup guide
- Best CAPTCHA Solvers Ranked — full provider leaderboard
Benchmark data sourced from CaptchaRank live performance monitoring. Success rates reflect current data and are updated as solver performance changes.