Scarnox · Blog

Resolver captchas en C# con HttpClient: ejemplo .NET (2026)

En C#, un solver de captchas es una pequeña clase alrededor de HttpClient. Envías la sitekey y la URL de la página a la API de Scarnox, lees el token de la respuesta JSON y lo envías al formulario del sitio. Esta guía construye esa clase para .NET 8, para PopularCaptcha, que Scarnox resuelve hoy sin proxy.

Empieza con crédito de prueba gratis, sin tarjeta y prueba el código contra una página que controles antes de ejecutarlo en cualquier otra.

Requisitos

Crear el proyecto

dotnet new console -n CaptchaDemo
cd CaptchaDemo

No hacen falta paquetes adicionales. System.Net.Http y System.Text.Json forman parte del framework.

La clase del solver

La clase siguiente posee un HttpClient con la cabecera Bearer y un timeout de 185 segundos. Devuelve el token o lanza una excepción con el texto de error de la API:

// ScarnoxSolver.cs
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;

public sealed class ScarnoxSolver : IDisposable
{
    private const string ApiUrl = "https://api.scarnox.com/api/tasks/create";
    private readonly HttpClient _http;

    public ScarnoxSolver(string apiKey)
    {
        _http = new HttpClient { Timeout = TimeSpan.FromSeconds(185) };
        _http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
    }

    public async Task<string> SolveAsync(
        string sitekey,
        string pageUrl,
        bool invisible = false,
        CancellationToken ct = default)
    {
        var body = new Dictionary<string, object>
        {
            ["captcha_type"] = "PopularCaptchaTaskProxyless",
            ["site_key"] = sitekey,
            ["site_url"] = pageUrl,
        };
        if (invisible)
        {
            body["is_invisible"] = true;
        }

        // The call returns when the token is ready. There is no polling.
        using var resp = await _http.PostAsJsonAsync(ApiUrl, body, ct);
        var data = await resp.Content.ReadFromJsonAsync<CreateTaskResponse>(cancellationToken: ct)
                   ?? throw new InvalidOperationException(
                       $"HTTP {(int)resp.StatusCode}: empty response");

        if (data.ErrorId != 0)
        {
            throw new InvalidOperationException($"{data.ErrorCode}: {data.ErrorDescription}");
        }

        return data.Solution?.Token
               ?? throw new InvalidOperationException("Response had no token");
    }

    public void Dispose() => _http.Dispose();
}

public sealed class CreateTaskResponse
{
    [JsonPropertyName("errorId")]
    public int ErrorId { get; set; }

    [JsonPropertyName("errorCode")]
    public string? ErrorCode { get; set; }

    [JsonPropertyName("errorDescription")]
    public string? ErrorDescription { get; set; }

    [JsonPropertyName("solution")]
    public Solution? Solution { get; set; }
}

public sealed class Solution
{
    [JsonPropertyName("token")]
    public string? Token { get; set; }
}

La comprobación de errorId es la línea importante. La API devuelve 0 en caso de éxito. Cualquier otro valor llega con errorCode y errorDescription, y la clase expone ambos.

Enviar el token

El token va al formulario del sitio. Usa un segundo HttpClient con un contenedor de cookies, de modo que la sesión que cargó el formulario se reutilice en el POST:

// Program.cs
using System.Net;

var apiKey = Environment.GetEnvironmentVariable("SCARNOX_API_KEY")
             ?? throw new InvalidOperationException("Set SCARNOX_API_KEY");

const string pageUrl = "https://example.com/signup";
const string sitekey = "YOUR-SITEKEY-UUID";

using var solver = new ScarnoxSolver(apiKey);
string token = "";

// Retry transient failures. Failed solves are not charged.
for (int attempt = 1; attempt <= 3; attempt++)
{
    try
    {
        token = await solver.SolveAsync(sitekey, pageUrl);
        break;
    }
    catch (InvalidOperationException ex) when (attempt < 3)
    {
        Console.Error.WriteLine($"Attempt {attempt} failed: {ex.Message}");
        await Task.Delay(TimeSpan.FromSeconds(attempt));
    }
}

var cookies = new CookieContainer();
var handler = new HttpClientHandler { CookieContainer = cookies, UseCookies = true };
using var site = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(60) };

// GET first so the site sets its session cookie.
await site.GetAsync(pageUrl);

using var form = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["email"] = "you@example.com",
    ["h-captcha-response"] = token,
    ["g-recaptcha-response"] = token,
});

using var response = await site.PostAsync(pageUrl, form);
Console.WriteLine($"Status: {(int)response.StatusCode}");

Ejecútalo:

set SCARNOX_API_KEY=scarnox_YOURAPIKEY
dotnet run

En PowerShell usa $env:SCARNOX_API_KEY = "scarnox_YOURAPIKEY" en su lugar.

Por qué está estructurado así

**Un HttpClient por solver.** HttpClient está pensado para reutilizarse, no para crearse en cada llamada. El solver posee uno y lo libera al final.

Un timeout de 185 segundos. La API se bloquea hasta que el token está listo. Un timeout más corto cancelaría resoluciones que estaban a punto de terminar.

Deserialización tipada. ReadFromJsonAsync mapea el JSON a CreateTaskResponse, así que las comprobaciones de error y de token se leen como C# normal.

El envío del formulario. El sitio recibe tanto h-captcha-response como g-recaptcha-response, ya que los sitios leen uno u otro. El contenedor de cookies mantiene la sesión entre el GET y el POST.

Tiempos

El token caduca en unos dos minutos. Resuelve justo antes de enviar. Si resuelves pronto en un lote y envías mucho después, el token estará caducado. Caducidad del token de captcha cubre los detalles.

Widgets invisibles

Pasa invisible: true a SolveAsync cuando el widget sea invisible. La petición incluirá entonces is_invisible: true. Algunas páginas necesitan un callback de JavaScript que un cliente del lado del servidor no puede activar. En ese caso, una herramienta de automatización de navegador es la mejor opción. La guía sobre captchas invisibles cubre los patrones.

Scrapea de forma responsable

Si el programa hace scraping de páginas públicas, revisa primero el robots.txt y los términos del sitio. Usa una pausa entre peticiones y conserva solo los datos personales que necesites. Para el contexto legal, consulta ¿es legal el web scraping?.

Precios

Scarnox cobra por cada resolución correcta, y las resoluciones fallidas son gratuitas. Consulta los precios actuales para ver las tarifas. Empieza con crédito de prueba gratis, sin tarjeta.

Próximos pasos