Um envio de GeeTest v3 do tipo slide só é aceito pela API se três campos chegarem exatamente certos: gt, challenge e pageurl. Na prática, a maioria dos erros de integração não vem da resolução do desafio em si — vem de um challenge reenviado depois de expirado, ou de um gt extraído do lugar errado da página. Este guia mostra onde cada parâmetro aparece no HTML, como extraí-los com Python e como montar a requisição para a API da CaptchaAI, do primeiro GET até a validação final no formulário do site. Se a sua suíte de QA automatiza um login atrás de um GeeTest v3 — um fluxo de cadastro que roda em staging antes de cada deploy, por exemplo — os quatro parâmetros abaixo cobrem o caso completo.
Os parâmetros que a API espera
Antes de escrever qualquer código, vale entender o papel de cada campo:
gt(obrigatório) — ID da conta GeeTest do site, em hexadecimal de 32 caracteres. Aparece no HTML renderizado ou na resposta da API de registro.challenge(obrigatório) — string de desafio específica da sessão; precisa ser nova a cada tentativa de solução.pageurl(obrigatório) — URL completa da página que exibe o CAPTCHA.api_server(opcional) — subdomínio personalizado do servidor GeeTest, só necessário quando o site não usa o padrão.
Em um fluxo de QA típico, os três primeiros campos vêm sempre da mesma extração; o quarto só entra em cena quando o site aponta para um endpoint GeeTest fora do comum — o assunto da seção "Quando informar api_server", mais adiante.
Como extrair gt e challenge de uma página
O gt costuma estar direto no HTML renderizado, embutido no JavaScript que inicializa o widget. Já o challenge normalmente só existe depois de uma chamada à rota de registro do GeeTest (algo como .../register-slide...), porque ele é gerado por sessão. A função abaixo tenta as duas fontes: primeiro procura gt no HTML, depois localiza o endpoint de registro e lê challenge — e um possível gt de fallback — da resposta JSON. Se nada aparecer no HTML estático, o site provavelmente monta o widget via JavaScript; esse caso tem solução própria mais adiante, na seção de erros comuns.
# extract_geetest_params.py
import requests
import re
import json
def extract_geetest_v3(page_url, session=None):
"""Extract GeeTest v3 gt and challenge from a page."""
if session is None:
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
)
resp = session.get(page_url, timeout=15)
html = resp.text
# Method 1: Extract gt from HTML
gt_match = re.search(r'gt["\']?\s*[:=]\s*["\']([a-f0-9]{32})', html)
gt = gt_match.group(1) if gt_match else None
# Method 2: Find API endpoint that returns challenge
api_match = re.search(r'(https?://[^"\']+register-slide[^"\']*)', html)
challenge = None
if api_match:
api_url = api_match.group(1)
api_resp = session.get(api_url, timeout=10)
try:
data = api_resp.json()
challenge = data.get("challenge")
gt = gt or data.get("gt")
except json.JSONDecodeError:
pass
if not challenge:
# Try embedded challenge
ch_match = re.search(r'challenge["\']?\s*[:=]\s*["\']([a-f0-9]+)', html)
challenge = ch_match.group(1) if ch_match else None
return {"gt": gt, "challenge": challenge, "pageurl": page_url}
# Usage
params = extract_geetest_v3("https://staging.example.com/qa-login")
print(f"gt: {params['gt']}")
print(f"challenge: {params['challenge']}")
Enviando o desafio para a API da CaptchaAI
Com os três campos em mãos, o envio segue o padrão in.php / res.php de qualquer tipo suportado pela CaptchaAI: você envia method=geetest com os parâmetros extraídos, recebe um task_id e faz o polling em res.php até status virar 1. Cada tarefa ocupa uma thread do seu plano — no BASIC (US$ 15/mês, 5 threads), dá para manter cinco desafios GeeTest em paralelo. O GeeTest v3 costuma resolver em menos de 12 segundos; por isso a função abaixo aguarda 10 s antes da primeira consulta e repete a cada 5 s, sem polling agressivo desde o primeiro segundo.
# solve_geetest.py
import requests
import time
import os
def solve_geetest(gt, challenge, pageurl, api_server=None):
"""Solve GeeTest v3 slide CAPTCHA via CaptchaAI."""
api_key = os.environ["CAPTCHAAI_API_KEY"]
payload = {
"key": api_key,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": pageurl,
"json": 1,
}
if api_server:
payload["api_server"] = api_server
# Submit
resp = requests.post(
"https://ocr.captchaai.com/in.php",
data=payload,
timeout=30,
)
result = resp.json()
if result.get("status") != 1:
raise RuntimeError(f"Submit failed: {result.get('request')}")
task_id = result["request"]
# Poll — GeeTest typically solves in 10-20 seconds
time.sleep(10)
for _ in range(30):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key,
"action": "get",
"id": task_id,
"json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
return data["request"] # Returns challenge, validate, seccode
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("GeeTest solve timeout")
Repassando a solução ao formulário do site
A resposta de res.php traz um único payload com três valores — challenge, validate e seccode — que precisam ser enviados ao endpoint de validação do próprio site, não de volta para a CaptchaAI. A função submit_geetest_solution faz o parse (a API às vezes devolve string, às vezes JSON já decodificado) e monta o POST no formato que o GeeTest espera, com os três campos prefixados por geetest_.
# submit_solution.py
import json
def submit_geetest_solution(session, validation_url, solution, original_challenge):
"""Submit GeeTest solution to the target site."""
# Parse solution if string
if isinstance(solution, str):
solution = json.loads(solution)
payload = {
"geetest_challenge": solution.get("challenge", original_challenge),
"geetest_validate": solution.get("validate", ""),
"geetest_seccode": solution.get("seccode", ""),
}
resp = session.post(validation_url, data=payload, timeout=30)
return resp
# Complete flow
def full_geetest_flow(page_url, validation_url):
import requests
from extract_geetest_params import extract_geetest_v3
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36"
)
# Step 1: Extract parameters
params = extract_geetest_v3(page_url, session)
print(f"gt: {params['gt']}, challenge: {params['challenge'][:16]}...")
# Step 2: Solve
solution = solve_geetest(
params["gt"], params["challenge"], params["pageurl"],
)
print("Solved!")
# Step 3: Submit
resp = submit_geetest_solution(
session, validation_url, solution, params["challenge"],
)
print(f"Validation response: {resp.status_code}")
return resp
Por que o challenge expira tão rápido
O challenge é amarrado à sessão que o gerou: assim que o navegador troca de página, atualiza o formulário ou simplesmente espera demais, o GeeTest invalida aquele valor do lado do servidor. Na prática isso abre uma janela de 60 a 120 segundos entre extrair o challenge e enviar a solução — e qualquer chamada de rede no meio do caminho, como um redirecionamento ou um retry de login, pode consumir boa parte dela. A rotina abaixo separa a extração do envio em duas funções, para deixar claro onde fica o ponto de não retorno: depois que get_fresh_challenge devolve o valor, o próximo passo tem que ser o envio à CaptchaAI, sem etapas intermediárias.
# fresh_challenge.py
import time
def get_fresh_challenge(session, register_url):
"""Always fetch a fresh challenge before solving."""
resp = session.get(register_url, timeout=10)
data = resp.json()
challenge = data.get("challenge")
if not challenge:
raise ValueError("No challenge returned")
return challenge
def solve_with_fresh_challenge(session, gt, register_url, pageurl):
"""Ensure challenge is fresh before submitting to CaptchaAI."""
challenge = get_fresh_challenge(session, register_url)
# Submit immediately — don't let it expire
solution = solve_geetest(gt, challenge, pageurl)
return solution
Extraia o challenge e envie para a CaptchaAI em segundos. Um challenge obsoleto sempre falha, não importa quão bem escrito esteja o resto da integração.
Quando informar api_server
A maioria dos sites usa o servidor GeeTest padrão (api.geetest.com), e nesse caso basta omitir api_server. Alguns sites, porém, apontam para um subdomínio próprio, geralmente por região (api-na.geetest.com) ou por uma rota personalizada (/ajax-custom). Se a sua suíte de testes roda a partir de uma região como sa-east-1 e o site também serve um endpoint GeeTest regional, confira as requisições de rede antes de assumir o padrão: o parâmetro errado aqui não gera erro imediato, só atrasa a resolução.
# The api_server parameter specifies a custom GeeTest backend
# Default: api.geetest.com
# Custom examples: api-na.geetest.com, api.geetest.com/ajax-custom
solution = solve_geetest(
gt="abc123...",
challenge="def456...",
pageurl="https://staging.example.com/qa-login",
api_server="api-na.geetest.com", # North America endpoint
)
Perguntas frequentes
O gt muda a cada tentativa ou fica fixo?
Fica fixo. gt é o identificador da conta GeeTest do site e não muda entre sessões — é o challenge que precisa ser novo a cada solução enviada.
Depois de quanto tempo o challenge expira?
Entre 60 e 120 segundos, dependendo do site. Extraia e envie para a CaptchaAI o quanto antes; challenges reaproveitados de uma execução de teste anterior sempre falham.
GeeTest v4 já é compatível com a CaptchaAI?
Ainda não — o suporte a GeeTest v4 está a caminho e hoje só pode ser descrito como "em breve". Este guia cobre o GeeTest v3, que a CaptchaAI resolve normalmente; para entender o que muda entre as versões, veja o guia dedicado ao v4.
Preciso de um navegador completo (Selenium) para extrair os parâmetros?
Só quando o widget é montado via JavaScript e o gt não aparece no HTML estático. Nesses casos, abrir a página com Selenium — ou capturar as respostas XHR diretamente — resolve; em páginas renderizadas no servidor, uma requisição HTTP simples costuma bastar.
O que fazer se a solução vier rejeitada mesmo com os três campos certos?
Confira se gt, challenge e pageurl vieram exatamente da mesma extração — um challenge de uma tentativa anterior, mesmo que pareça válido, gera seccode incompatível do lado do GeeTest. Refaça a extração do zero antes de reenviar.
Erros comuns e como resolver
A maioria dos problemas de integração cai em uma destas quatro causas, na ordem em que vale investigar.
ERROR_CAPTCHA_UNSOLVABLE
Challenge obsoleto. Extraia um novo imediatamente antes de enviar — não reaproveite um valor de uma tentativa anterior, mesmo que pareça recente.
validate vazio na resposta
Os três campos não vieram da mesma sessão. Confirme que gt, challenge e pageurl são da mesma extração; misturar sessões diferentes invalida a resposta.
Solução rejeitada pelo site
Falta o seccode no POST de validação. Garanta que os três campos — geetest_challenge, geetest_validate e geetest_seccode — sejam enviados juntos.
Parâmetro gt não encontrado no HTML
O widget é carregado via JavaScript. Use Selenium (ou outro navegador automatizado) ou inspecione as respostas XHR até localizar o endpoint de registro.
Guias relacionados
Domine os parâmetros do GeeTest v3 — comece com a CaptchaAI.