Resolver captchas en Go: ejemplo funcional con net/http (2026)
Un solver de captchas en Go no necesita paquetes de terceros. net/http y encoding/json de la biblioteca estándar cubren todo el flujo: un POST a la API de Scarnox, una respuesta JSON y un envío del formulario al sitio. Esta guía construye ese flujo para PopularCaptcha, que Scarnox resuelve hoy sin proxy.
Empieza con crédito de prueba gratis, sin tarjeta y ejecuta el programa contra una página que controles antes de usarlo en cualquier otro sitio.
Requisitos
- Go 1.21 o posterior.
- Una clave de API de Scarnox, leída de una variable de entorno.
- La sitekey del widget de PopularCaptcha de la página. Si necesitas ayuda para encontrarla, consulta cómo encontrar la sitekey de un captcha.
El programa completo
Este único archivo resuelve el captcha, reintenta los fallos temporales y envía el token al formulario. Guárdalo 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"
// The solve call blocks until the token is ready, so the timeout is long.
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 retries transient failures. Failed solves are not charged.
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)
}
// A cookie jar keeps the session the site ties the captcha to.
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)
}
Ejecútalo:
export SCARNOX_API_KEY=scarnox_YOURAPIKEY
go run main.go
Cómo encajan las piezas
La petición. solveCaptcha serializa una estructura en el cuerpo JSON que espera la API y establece la cabecera Authorization. El prefijo Bearer es obligatorio.
El timeout. La API se bloquea hasta que el token está listo, así que el timeout del cliente es de 185 segundos. No lo configures más bajo, o cancelarás resoluciones que estaban a punto de terminar.
La comprobación de errores. La API devuelve errorId como 0 en caso de éxito. Cualquier otro valor llega con errorCode y errorDescription, y la función los devuelve como error. El token está en solution.token.
El envío. El token va en el campo h-captcha-response, y g-recaptcha-response recibe el mismo valor. La mayoría de los sitios leen uno u otro.
Usa un cookie jar
Muchas páginas de registro y búsqueda vinculan el captcha a una sesión. El cookiejar del cliente del sitio mantiene esas cookies entre el GET que muestra el formulario y el POST que lo envía. Si lo omites, el sitio puede rechazar un token que por lo demás era válido.
Tiempos
El token caduca en unos dos minutos, así que resuelve justo antes de enviar. No resuelvas al principio de un trabajo largo y luego esperes. Caducidad del token de captcha explica cómo dimensionar la ventana.
Widgets invisibles
Si el widget de la página es invisible, pon el argumento invisible a true. La petición incluirá entonces is_invisible: true. Algunas páginas también necesitan que se llame a una función callback con el token. Busca esa función en el script de la página. La guía sobre captchas invisibles recoge los patrones.
Concurrencia
Go facilita resolver muchos captchas a la vez con goroutines. Mantén el número de resoluciones concurrentes dentro de lo que pueden asumir tu cuenta y el sitio de destino. Un dominio no debería recibir una ráfaga de peticiones, aunque tu solver pueda seguir el ritmo. Respeta el robots.txt y los términos del sitio, y limita el scraping a datos públicos que tengas permiso para recopilar.
Precios
Scarnox cobra por cada resolución correcta. Las resoluciones fallidas son gratuitas. Consulta los precios actuales para ver las tarifas. Para probar la API, empieza con crédito de prueba gratis, sin tarjeta.