Troubleshooting

Erros comuns do reCAPTCHA Invisible e como corrigir

Se o formulário "trava" depois que você injeta o token — sem erro, sem redirecionamento, só silêncio — o problema quase nunca é o token em si. Na maioria dos casos é o callback que nunca foi disparado: o reCAPTCHA Invisible depende de uma função JavaScript para de fato submeter o formulário, e injetar o token sozinho não basta.

Esse é só um dos quatro erros que respondem pela maior parte das falhas em automação com reCAPTCHA Invisible. Os outros três: o widget não é detectado porque não existe caixa de seleção visível para procurar; o parâmetro invisible=1 fica de fora da requisição à CaptchaAI; e o token expira porque foi resolvido cedo demais e ficou parado na fila até o envio. Este guia cobre os sete padrões de erro mais comuns, com a causa exata e a correção pronta para aplicar. Se você ainda não conhece o mecanismo por trás do widget invisível, comece por Como funciona o reCAPTCHA Invisible e como resolvê-lo.


Tabela rápida: erro, causa e correção

Erro Causa Correção
ERROR_WRONG_GOOGLEKEY Sitekey errada ou de outro domínio Extraia a sitekey da div do widget invisível ou da chamada grecaptcha.render()
ERROR_PAGEURL URL trocada — foi enviada a URL da página pai em vez da URL onde o widget carrega Use exatamente a URL onde o widget invisível aparece
ERROR_CAPTCHA_UNSOLVABLE O Google marcou a tarefa como impossível Repita com proxy e cookies novos; confira se o site migrou para v3
ERROR_BAD_TOKEN_OR_PAGEURL O site de destino rejeitou o token Confira se o pageurl bate exatamente; injete via callback, não em campo oculto
CAPCHA_NOT_READY A tarefa ainda está em processamento Continue consultando a cada 5 s; o reCAPTCHA Invisible normalmente resolve em menos de 30 s
ERROR_KEY_DOES_NOT_EXIST Chave de API da CaptchaAI inválida Confira a chave em captchaai.com/account
Token aceito, mas o formulário não reage O callback não foi executado depois da injeção Localize e chame a função de data-callback passando o token

Erro 1: o CaptchaAI não encontra o widget invisível na página

O reCAPTCHA Invisible não tem caixa de seleção visível — não há nada para o seu scraper "clicar" ou sequer perceber.

Se a detecção falhar, as requisições protegidas pelo captcha caem silenciosamente: sem exceção óbvia, só formulários que nunca são aceitos ou redirecionamentos estranhos.

Como identificar o reCAPTCHA Invisible no HTML

Procure estes três padrões na página:

<!-- Pattern 1: div with data-size="invisible" -->
<div class="g-recaptcha" data-sitekey="6LdKlZEU..."
     data-size="invisible"
     data-callback="onSubmit"></div>

<!-- Pattern 2: button with data-sitekey and invisible size -->
<button class="g-recaptcha"
        data-sitekey="6LdKlZEU..."
        data-callback="onSubmit"
        data-action="submit">Submit</button>

<!-- Pattern 3: programmatic render with size: invisible -->
<script>
  grecaptcha.render('submit-btn', {
    sitekey: '6LdKlZEU...',
    callback: onSubmit,
    size: 'invisible'
  });
</script>

Detecção em Python

import requests
from bs4 import BeautifulSoup
import re

def detect_invisible_recaptcha(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Check for data-size="invisible"
    widget = soup.find("div", {"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return {
            "type": "invisible",
            "sitekey": widget.get("data-sitekey"),
            "callback": widget.get("data-callback")
        }

    # Check for programmatic render with invisible
    scripts = soup.find_all("script")
    for script in scripts:
        if script.string and "size" in str(script.string) and "invisible" in str(script.string):
            key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
            if key_match:
                return {
                    "type": "invisible-programmatic",
                    "sitekey": key_match.group(1),
                    "callback": "check grecaptcha.render() call"
                }

    return None

A mesma checagem em Node.js

const axios = require("axios");
const cheerio = require("cheerio");

async function detectInvisibleRecaptcha(url) {
  const { data } = await axios.get(url);
  const $ = cheerio.load(data);

  // Check for data-size="invisible"
  const widget = $(".g-recaptcha[data-size='invisible']");
  if (widget.length) {
    return {
      type: "invisible",
      sitekey: widget.attr("data-sitekey"),
      callback: widget.attr("data-callback"),
    };
  }

  // Check script tags for programmatic invisible render
  const scriptContent = $("script")
    .map((_, el) => $(el).html())
    .get()
    .join("\n");
  if (scriptContent.includes("invisible")) {
    const keyMatch = scriptContent.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
    if (keyMatch) {
      return {
        type: "invisible-programmatic",
        sitekey: keyMatch[1],
        callback: "check grecaptcha.render() call",
      };
    }
  }

  return null;
}

Erro 2: ERROR_WRONG_GOOGLEKEY — a sitekey não é a do widget invisível

Esse erro aparece quando a sitekey enviada não é a do widget reCAPTCHA Invisible que está de fato na página de destino.

As causas mais comuns: a sitekey foi copiada de uma caixa de seleção v2 padrão em outra página do mesmo site; a sitekey veio da URL âncora de uma versão diferente do reCAPTCHA; ou a página tem mais de um widget reCAPTCHA e o scraper pegou o widget errado.

Como extrair a sitekey certa

import requests
from bs4 import BeautifulSoup

def get_invisible_sitekey(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Priority 1: invisible widget
    widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
    if widget:
        return widget["data-sitekey"]

    # Priority 2: any g-recaptcha div (may be invisible without data-size)
    widget = soup.find(class_="g-recaptcha")
    if widget and widget.get("data-sitekey"):
        return widget["data-sitekey"]

    return None

sitekey = get_invisible_sitekey("https://staging.example.com/qa-login")
print(f"Sitekey: {sitekey}")

Erro 3: o callback nunca dispara — o token foi aceito, mas nada acontece

Essa é a falha mais comum do reCAPTCHA Invisible, e a mais fácil de não perceber. Na caixa de seleção v2 padrão, colocar o token em g-recaptcha-response já é suficiente. No widget invisível, quase sempre existe uma função JavaScript de callback que precisa ser chamada manualmente — sem ela, o token fica parado no campo oculto e o formulário nunca é submetido.

Como o callback funciona

Primeiro, grecaptcha.execute() dispara o desafio invisível. Em seguida, depois de resolvido, o Google chama a função definida em data-callback. Por fim, é essa função que efetivamente envia o formulário ou dispara a chamada de API.

Correção: localize o callback e chame-o manualmente

Passo 1 — identifique o nome da função

# From HTML: data-callback="onSubmit"
# From JS: callback: onSubmit
# From grecaptcha.render: second argument with callback property

Passo 2 — injete o token e chame o callback (Selenium)

from selenium import webdriver
import requests
import time

driver = webdriver.Chrome()
driver.get("https://example.com/form")

# Get sitekey
sitekey = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-sitekey")
callback_name = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")

# Solve with CaptchaAI
task_id = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": driver.current_url,
    "invisible": 1
}).text.split("|")[1]

# Poll for result
token = None
for _ in range(60):
    time.sleep(5)
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY",
        "action": "get",
        "id": task_id
    }).text
    if resp.startswith("OK|"):
        token = resp.split("|")[1]
        break

# Inject token into the response field
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# CRITICAL: Call the callback function
driver.execute_script(f'{callback_name}("{token}");')

Passo 2 — a mesma lógica com Puppeteer

const puppeteer = require("puppeteer");
const axios = require("axios");

(async () => {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();
  await page.goto("https://example.com/form");

  // Get sitekey and callback
  const { sitekey, callback } = await page.evaluate(() => {
    const el = document.querySelector(".g-recaptcha[data-size='invisible']");
    return {
      sitekey: el?.getAttribute("data-sitekey"),
      callback: el?.getAttribute("data-callback"),
    };
  });

  // Submit to CaptchaAI
  const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: "YOUR_API_KEY",
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: page.url(),
      invisible: 1,
    },
  });
  const taskId = submitResp.data.split("|")[1];

  // Poll for result
  let token;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: "YOUR_API_KEY", action: "get", id: taskId },
    });
    if (result.data.startsWith("OK|")) {
      token = result.data.split("|")[1];
      break;
    }
  }

  // Inject token and fire callback
  await page.evaluate(
    (tok, cb) => {
      document.getElementById("g-recaptcha-response").value = tok;
      if (cb && typeof window[cb] === "function") {
        window[cb](tok);
      }
    },
    token,
    callback,
  );

  await browser.close();
})();

Erro 4: esqueceu o invisible=1 na requisição

Resolver reCAPTCHA Invisible pela CaptchaAI exige o parâmetro invisible=1 na chamada a in.php.

Sem ele, o solver trata a tarefa como uma caixa de seleção v2 comum — o resultado costuma ser ERROR_CAPTCHA_UNSOLVABLE ou um token que o site de destino recusa.

Requisição errada vs. requisição correta

# WRONG — missing invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url
}

# CORRECT — includes invisible=1
params = {
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": sitekey,
    "pageurl": page_url,
    "invisible": 1  # Required for invisible reCAPTCHA
}

response = requests.get("https://ocr.captchaai.com/in.php", params=params)

Erro 5: o token expira antes do envio (janela de 120 s)

Um token de reCAPTCHA Invisible vale por 120 segundos, igual ao v2 padrão. A diferença é que fluxos invisíveis costumam ter mais etapas entre resolver e enviar — preencher outros campos, validar no cliente, esperar uma resposta anterior — e cada etapa consome parte dessa janela.

Como identificar esse sintoma

O formulário devolve um erro genérico depois do envio de teste ao endpoint de QA, o siteverify do lado do servidor responde timeout-or-duplicate, e o token era válido no momento em que chegou, mas demorou demais para ser usado.

Correção: resolva só na hora de enviar

Peça a resolução no último momento possível, imediatamente antes do envio:

import requests
import time

def solve_invisible_recaptcha(api_key, sitekey, page_url):
    # Submit task
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    })
    if not resp.text.startswith("OK|"):
        raise Exception(f"Submit failed: {resp.text}")
    task_id = resp.text.split("|")[1]

    # Poll for result
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id
        })
        if result.text.startswith("OK|"):
            return result.text.split("|")[1]
        if result.text != "CAPCHA_NOT_READY":
            raise Exception(f"Solve failed: {result.text}")

    raise Exception("Solve timed out after 5 minutes")

# Usage: solve JUST before you need to submit
# 1. Navigate to page and prepare form data first
# 2. THEN solve the captcha
# 3. Inject token and submit immediately
token = solve_invisible_recaptcha("YOUR_API_KEY", sitekey, page_url)
# Submit within 120 seconds of receiving the token

Erro 6: ERROR_BAD_TOKEN_OR_PAGEURL — o Google recusou o token

O site de destino verificou o token junto ao Google e a verificação falhou. As causas mais comuns:

  • pageurl errado — não bate com o domínio registrado na sitekey; use exatamente a URL onde o widget carrega.
  • Token usado em outro domínio — o mesmo token foi reaproveitado entre domínios; resolva de novo com o pageurl do domínio correto.
  • Token já usado — o mesmo token foi enviado duas vezes; peça uma solução nova para cada envio.
  • IP incompatível — o IP do solver é diferente do IP da sua sessão; adicione o parâmetro proxy para casar com o IP da sessão.
  • Sem a flag invisível — resolvido como v2 padrão, mas usado numa página invisível; adicione invisible=1 à requisição de resolução.

O caso do IP incompatível é comum quando os workers de automação rodam numa região diferente da sessão que gerou a sitekey — por exemplo, workers em sa-east-1 (São Paulo) resolvendo para uma sessão que passou por um proxy hospedado em outra região. O Google associa o token ao IP que fez a verificação; se o IP da submissão do formulário não bate com o IP da resolução, o siteverify falha mesmo com um token tecnicamente válido. Alinhar o proxy da chamada in.php com o IP real da sessão resolve a maior parte desses casos.

Checklist de depuração

def debug_invisible_solve(api_key, sitekey, page_url, proxy=None):
    """Run a diagnostic solve with detailed logging."""
    print(f"Sitekey: {sitekey}")
    print(f"Page URL: {page_url}")
    print(f"Proxy: {proxy or 'none'}")

    params = {
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "invisible": 1
    }
    if proxy:
        params["proxy"] = proxy
        params["proxytype"] = "HTTP"

    # Submit
    resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
    print(f"Submit response: {resp.text}")
    if not resp.text.startswith("OK|"):
        return None

    task_id = resp.text.split("|")[1]
    print(f"Task ID: {task_id}")

    # Poll with timing
    start = time.time()
    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get", "id": task_id
        })
        elapsed = time.time() - start
        print(f"  [{elapsed:.0f}s] {result.text[:50]}")
        if result.text.startswith("OK|"):
            token = result.text.split("|")[1]
            print(f"Token received after {elapsed:.0f}s")
            print(f"Token length: {len(token)} characters")
            print(f"Token starts with: {token[:30]}...")
            return token
        if result.text != "CAPCHA_NOT_READY":
            print(f"FAILED: {result.text}")
            return None

    print("TIMEOUT after 5 minutes")
    return None

Erro 7: a página tem mais de um widget reCAPTCHA

Algumas páginas combinam uma caixa de seleção v2 visível com um reCAPTCHA Invisible — por exemplo, login com v2 e cadastro com invisível na mesma tela.

Resolver o widget errado gera um token válido, só que para o desafio errado: ele não libera a ação que você realmente precisa executar.

Correção: identifique cada widget antes de resolver

from bs4 import BeautifulSoup

def find_all_recaptcha_widgets(html):
    soup = BeautifulSoup(html, "html.parser")
    widgets = []

    for el in soup.find_all(class_="g-recaptcha"):
        widgets.append({
            "sitekey": el.get("data-sitekey"),
            "size": el.get("data-size", "normal"),
            "callback": el.get("data-callback"),
            "tag": el.name,
            "id": el.get("id")
        })

    return widgets

# Example output:
# [
#   {"sitekey": "6LdA...", "size": "normal", "callback": None, "tag": "div", "id": "recaptcha-login"},
#   {"sitekey": "6LdB...", "size": "invisible", "callback": "onRegister", "tag": "div", "id": "recaptcha-register"}
# ]
# Use the widget with size="invisible" for the invisible solve

Solver completo com retentativa automática

Esta classe reúne as correções dos sete erros acima num wrapper pronto para produção, com retentativa exponencial e tratamento explícito para cada resposta de erro da CaptchaAI:

import requests
import time
import logging

logger = logging.getLogger(__name__)

class InvisibleRecaptchaSolver:
    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries
        self.base_url = "https://ocr.captchaai.com"

    def solve(self, sitekey, page_url, proxy=None):
        """Solve invisible reCAPTCHA with automatic retry on transient errors."""
        for attempt in range(1, self.max_retries + 1):
            try:
                token = self._attempt_solve(sitekey, page_url, proxy)
                if token:
                    return token
            except Exception as e:
                logger.warning(f"Attempt {attempt} failed: {e}")
                if attempt < self.max_retries:
                    time.sleep(2 ** attempt)
        raise Exception(f"Failed to solve after {self.max_retries} attempts")

    def _attempt_solve(self, sitekey, page_url, proxy):
        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url,
            "invisible": 1
        }
        if proxy:
            params["proxy"] = proxy
            params["proxytype"] = "HTTP"

        # Submit task
        resp = requests.get(f"{self.base_url}/in.php", params=params)

        if "ERROR" in resp.text:
            error = resp.text.strip()
            if error in ("ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"):
                raise Exception(f"Configuration error (do not retry): {error}")
            if error == "ERROR_ZERO_BALANCE":
                raise Exception("Account balance is zero — add funds")
            raise Exception(f"Submit error: {error}")

        if not resp.text.startswith("OK|"):
            raise Exception(f"Unexpected submit response: {resp.text}")

        task_id = resp.text.split("|")[1]

        # Poll for result
        for _ in range(60):
            time.sleep(5)
            result = requests.get(f"{self.base_url}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id
            })

            if result.text.startswith("OK|"):
                return result.text.split("|")[1]

            if result.text == "CAPCHA_NOT_READY":
                continue

            if result.text == "ERROR_CAPTCHA_UNSOLVABLE":
                logger.warning("Captcha unsolvable — will retry with new task")
                return None

            raise Exception(f"Poll error: {result.text}")

        raise Exception("Solve timed out after 5 minutes")


# Usage
solver = InvisibleRecaptchaSolver("YOUR_API_KEY")
token = solver.solve(
    sitekey="6LdKlZEU...",
    page_url="https://staging.example.com/qa-login"
)
print(f"Token: {token[:50]}...")

A mesma classe em Node.js:

const axios = require("axios");

class InvisibleRecaptchaSolver {
  constructor(apiKey, maxRetries = 3) {
    this.apiKey = apiKey;
    this.maxRetries = maxRetries;
    this.baseUrl = "https://ocr.captchaai.com";
  }

  async solve(sitekey, pageUrl, proxy) {
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const token = await this._attemptSolve(sitekey, pageUrl, proxy);
        if (token) return token;
      } catch (err) {
        console.warn(`Attempt ${attempt} failed: ${err.message}`);
        if (attempt < this.maxRetries) {
          await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
        }
      }
    }
    throw new Error(`Failed to solve after ${this.maxRetries} attempts`);
  }

  async _attemptSolve(sitekey, pageUrl, proxy) {
    const params = {
      key: this.apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageUrl,
      invisible: 1,
    };
    if (proxy) {
      params.proxy = proxy;
      params.proxytype = "HTTP";
    }

    // Submit task
    const submitResp = await axios.get(`${this.baseUrl}/in.php`, { params });
    if (submitResp.data.includes("ERROR")) {
      const error = submitResp.data.trim();
      if (["ERROR_WRONG_GOOGLEKEY", "ERROR_KEY_DOES_NOT_EXIST"].includes(error)) {
        throw new Error(`Configuration error (do not retry): ${error}`);
      }
      throw new Error(`Submit error: ${error}`);
    }

    const taskId = submitResp.data.split("|")[1];

    // Poll for result
    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const result = await axios.get(`${this.baseUrl}/res.php`, {
        params: { key: this.apiKey, action: "get", id: taskId },
      });

      if (result.data.startsWith("OK|")) {
        return result.data.split("|")[1];
      }
      if (result.data === "CAPCHA_NOT_READY") continue;
      if (result.data === "ERROR_CAPTCHA_UNSOLVABLE") return null;
      throw new Error(`Poll error: ${result.data}`);
    }
    throw new Error("Solve timed out after 5 minutes");
  }
}

// Usage
const solver = new InvisibleRecaptchaSolver("YOUR_API_KEY");
solver.solve("6LdKlZEU...", "https://staging.example.com/qa-login").then((token) => {
  console.log(`Token: ${token.substring(0, 50)}...`);
});

Checklist antes de pedir ajuda no suporte

Rode esta lista quando o reCAPTCHA Invisible continuar falhando depois das correções acima:

  1. É realmente invisível, não v2 padrão? Procure data-size="invisible" ou size: 'invisible' na chamada de render.
  2. A sitekey é a do widget certo? Compare com o data-sitekey do widget marcado como invisível, não de outro na mesma página.
  3. invisible=1 está na requisição? Confira os parâmetros enviados a in.php.
  4. pageurl bate exatamente? Copie a URL pelo DevTools do navegador, não uma URL de redirecionamento.
  5. O nome do callback foi identificado? Procure data-callback no HTML ou callback em grecaptcha.render().
  6. O token foi injetado E o callback foi chamado? As duas etapas são obrigatórias — o token sozinho não movimenta o formulário.
  7. O token ainda está dentro da validade? Precisa ser usado em até 120 segundos após a resolução.
  8. Testou com proxy quando o IP importa? Envie os parâmetros proxy e proxytype.

Perguntas frequentes

O reCAPTCHA Invisible e o reCAPTCHA v3 são a mesma coisa?

Não. Os dois evitam interação visível, mas são APIs diferentes. O reCAPTCHA Invisible é uma variante do v2 (data-size="invisible") e continua usando uma sitekey de v2 mais o parâmetro invisible=1 na CaptchaAI. Já o reCAPTCHA v3 roda via grecaptcha.execute() com uma action e devolve um score, sem qualquer callback de desafio. Confundir os dois é uma causa frequente de ERROR_WRONG_GOOGLEKEY — confirme que a sitekey realmente veio de um widget v2 marcado como invisível.

CAPCHA_NOT_READY repetido algumas vezes é sinal de problema?

Não. É a resposta normal enquanto o worker ainda processa a tarefa — o reCAPTCHA Invisible normalmente resolve em menos de 30 segundos, e um polling a cada 5 segundos costuma retornar CAPCHA_NOT_READY duas ou três vezes antes do token. Só trate como falha se o loop ultrapassar os 5 minutos de timeout; nesse caso, descarte a tarefa e envie uma nova em vez de continuar consultando a mesma id.

Por que o token funciona em teste, mas falha em produção?

O motivo mais comum é incompatibilidade de IP. Em teste, o IP do solver e o IP de quem está testando muitas vezes ficam próximos; em produção, o IP do solver e o IP do seu servidor são diferentes. Adicione um parâmetro proxy que corresponda ao IP real da sessão de produção para eliminar essa causa (veja o Erro 6).

Quanto tempo o CaptchaAI leva para resolver o reCAPTCHA Invisible?

Normalmente menos de 30 segundos. Desafios invisíveis costumam ter menor latência que a caixa de seleção v2 porque dependem de análise de risco, não de reconhecimento de imagem — mas o tempo total do seu fluxo também inclui o polling e a execução do callback, então meça de ponta a ponta no seu próprio ambiente.


Próximas etapas

Artigos relacionados

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