Solver de captcha em C# com HttpClient: exemplo com .NET (2026)
Em C#, um solver de captcha é uma pequena classe em volta do HttpClient. Você envia o sitekey e a URL da página para a API da Scarnox, lê o token da resposta JSON e o envia ao formulário do site. Este guia constrói essa classe para o .NET 8, para o PopularCaptcha, que a Scarnox resolve hoje de forma proxyless.
Comece com crédito de teste grátis, sem cartão e teste o código em uma página que você controla antes de rodá-lo em qualquer outro lugar.
Requisitos
- SDK do .NET 8 (
dotnet --version). - Uma chave de API da Scarnox, lida de uma variável de ambiente.
- O sitekey do widget PopularCaptcha da página. Para ajuda para encontrá-lo, veja como encontrar o sitekey de um captcha.
Criando o projeto
dotnet new console -n CaptchaDemo
cd CaptchaDemo
Nenhum pacote extra é necessário. System.Net.Http e System.Text.Json fazem parte do framework.
A classe solver
A classe abaixo mantém um HttpClient com o cabeçalho Bearer e um timeout de 185 segundos. Ela devolve o token ou lança uma exceção com o texto de erro da 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;
}
// A chamada retorna quando o token está pronto. Não há 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; }
}
A verificação de errorId é a linha mais importante. A API retorna 0 em caso de sucesso. Qualquer outro valor vem com errorCode e errorDescription, e a classe expõe os dois.
Enviando o token
O token vai para o formulário do site. Use um segundo HttpClient com um contêiner de cookies, para que a sessão que carregou o formulário seja reaproveitada no 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 = "";
// Refaz falhas transitórias. Resoluções com falha não são cobradas.
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 primeiro para que o site defina o cookie de sessão.
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}");
Execute:
set SCARNOX_API_KEY=scarnox_YOURAPIKEY
dotnet run
No PowerShell, use $env:SCARNOX_API_KEY = "scarnox_YOURAPIKEY" no lugar.
Por que é estruturado assim
**Um HttpClient por solver.** O HttpClient foi feito para ser reaproveitado, não criado a cada chamada. O solver mantém um e o descarta no fim.
Timeout de 185 segundos. A API fica bloqueada até o token estar pronto. Um timeout menor cancelaria resoluções que estavam prestes a terminar.
Desserialização tipada. ReadFromJsonAsync mapeia o JSON para CreateTaskResponse, então as verificações de erro e de token ficam como C# comum.
O envio do formulário. O site recebe h-captcha-response e g-recaptcha-response, já que cada site lê um ou outro. O contêiner de cookies mantém a sessão entre o GET e o POST.
Tempo
O token expira em cerca de dois minutos. Resolva logo antes de enviar. Se você resolver cedo em um lote e enviar muito depois, o token estará vencido. Expiração de token de captcha cobre os detalhes.
Widgets invisíveis
Passe invisible: true para SolveAsync quando o widget for invisível. A requisição então inclui is_invisible: true. Algumas páginas precisam de um callback em JavaScript que um cliente do lado do servidor não consegue disparar. Nesse caso, uma ferramenta de automação de navegador é a escolha melhor. O guia captcha invisível cobre os padrões.
Faça scraping com responsabilidade
Se o programa faz scraping de páginas públicas, verifique antes o robots.txt e os termos do site. Use um intervalo entre as requisições e guarde apenas os dados pessoais de que você precisa. Para o contexto legal, veja web scraping é legal.
Preços
A Scarnox cobra por resolução bem-sucedida, e resoluções com falha são gratuitas. Veja os preços atuais para as tarifas. Comece com crédito de teste grátis, sem cartão.