Scarnox · Blog

Solver de captcha em Go com net/http: exemplo funcional (2026)

Um solver de captcha em Go não precisa de pacotes de terceiros. O net/http e o encoding/json da biblioteca padrão cobrem todo o fluxo: um POST para a API da Scarnox, uma resposta JSON e o envio do formulário ao site. Este guia constrói esse fluxo para o PopularCaptcha, que a Scarnox resolve hoje de forma proxyless.

Comece com crédito de teste grátis, sem cartão e rode o programa em uma página que você controla antes de usá-lo em outro lugar.

Requisitos

O programa completo

Este arquivo único resolve o captcha, refaz as falhas transitórias e envia o token ao formulário. Salve-o como main.go:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"time"
)

const apiURL = "https://api.scarnox.com/api/tasks/create"

// A chamada de resolução fica bloqueada até o token estar pronto, por isso o timeout é longo.
var apiClient = &http.Client{Timeout: 185 * time.Second}

type createRequest struct {
	CaptchaType string `json:"captcha_type"`
	SiteKey     string `json:"site_key"`
	SiteURL     string `json:"site_url"`
	IsInvisible bool   `json:"is_invisible,omitempty"`
}

type createResponse struct {
	ErrorID          int    `json:"errorId"`
	ErrorCode        string `json:"errorCode"`
	ErrorDescription string `json:"errorDescription"`
	Solution         struct {
		Token string `json:"token"`
	} `json:"solution"`
}

func solveCaptcha(apiKey, sitekey, pageURL string, invisible bool) (string, error) {
	body, err := json.Marshal(createRequest{
		CaptchaType: "PopularCaptchaTaskProxyless",
		SiteKey:     sitekey,
		SiteURL:     pageURL,
		IsInvisible: invisible,
	})
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewReader(body))
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := apiClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var out createResponse
	if err := json.Unmarshal(raw, &out); err != nil {
		return "", fmt.Errorf("HTTP %d, response was not JSON: %s", resp.StatusCode, raw)
	}
	if out.ErrorID != 0 {
		return "", fmt.Errorf("%s: %s", out.ErrorCode, out.ErrorDescription)
	}
	return out.Solution.Token, nil
}

// solveWithRetry refaz falhas transitórias. Resoluções com falha não são cobradas.
func solveWithRetry(apiKey, sitekey, pageURL string, attempts int) (string, error) {
	var lastErr error
	for i := 1; i <= attempts; i++ {
		token, err := solveCaptcha(apiKey, sitekey, pageURL, false)
		if err == nil {
			return token, nil
		}
		lastErr = err
		log.Printf("attempt %d failed: %v", i, err)
		time.Sleep(time.Duration(i) * time.Second)
	}
	return "", lastErr
}

func main() {
	apiKey := os.Getenv("SCARNOX_API_KEY")
	if apiKey == "" {
		log.Fatal("set SCARNOX_API_KEY")
	}

	const pageURL = "https://example.com/signup"
	const sitekey = "YOUR-SITEKEY-UUID"

	token, err := solveWithRetry(apiKey, sitekey, pageURL, 3)
	if err != nil {
		log.Fatal(err)
	}

	// Um cookie jar mantém a sessão à qual o site vincula o captcha.
	jar, err := cookiejar.New(nil)
	if err != nil {
		log.Fatal(err)
	}
	site := &http.Client{Jar: jar, Timeout: 60 * time.Second}

	form := url.Values{}
	form.Set("email", "you@example.com")
	form.Set("h-captcha-response", token)
	form.Set("g-recaptcha-response", token)

	resp, err := site.PostForm(pageURL, form)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	fmt.Println("Status:", resp.Status)
}

Execute:

export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go

Como as peças se encaixam

A requisição. solveCaptcha converte uma struct no corpo JSON que a API espera e define o cabeçalho Authorization. O prefixo Bearer é obrigatório.

O timeout. A API fica bloqueada até o token estar pronto, então o timeout do cliente é de 185 segundos. Não defina um valor menor, senão você cancelará resoluções que estavam prestes a terminar.

A verificação de erro. A API retorna errorId com 0 em caso de sucesso. Qualquer outro valor vem com errorCode e errorDescription, e a função devolve esses dados como erro. O token fica em solution.token.

O envio. O token vai para o campo h-captcha-response, e g-recaptcha-response recebe o mesmo valor. A maioria dos sites lê um ou outro.

Muitas páginas de cadastro e busca vinculam o captcha a uma sessão. O cookiejar no cliente do site mantém esses cookies entre o GET que mostra o formulário e o POST que o envia. Se você pular essa etapa, o site pode rejeitar um token que, de outra forma, seria válido.

Tempo

O token expira em cerca de dois minutos, então resolva logo antes de enviar. Não resolva no início de um job longo e depois espere. Expiração de token de captcha explica como dimensionar essa janela.

Widgets invisíveis

Se o widget da página for invisível, defina o argumento invisible como true. A requisição então inclui is_invisible: true. Algumas páginas também precisam de uma função de callback chamada com o token. Verifique o script da página para encontrá-la. O guia captcha invisível lista os padrões.

Concorrência

Em Go é fácil resolver muitos captchas ao mesmo tempo com goroutines. Mantenha o número de resoluções simultâneas dentro do que a sua conta e o site alvo conseguem suportar. Um único domínio não deve receber uma rajada de requisições, mesmo que o seu solver consiga acompanhar. Respeite o robots.txt e os termos do site, e limite o scraping a dados públicos que você tem permissão para coletar.

Preços

A Scarnox cobra por resolução bem-sucedida. Resoluções com falha são gratuitas. Veja os preços atuais para as tarifas. Para testar a API, comece com crédito de teste grátis, sem cartão.

Próximos passos