API Tutorials

GeeTest v3 slide: parâmetros gt, challenge e API da CaptchaAI

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.

Os comentários estão desativados para este artigo.