CAPTCHA Solving 7 min read

Solving reCAPTCHA in C# with the 2Captcha Service

Step-by-step 2Captcha integration in C#: send reCAPTCHA tasks to the API, poll for solution tokens, and submit forms automatically. Get the full code example.

ST
Scraping.Pro Team
Data collection for business needs
Published: 29 June 2026

This guide shows how to integrate the 2captcha solving service into a C# application so that a Google reCAPTCHA can be passed programmatically. It covers the underlying workflow, two ways to talk to the API (the official NuGet library and a raw HTTP implementation), and a complete end-to-end example that submits a form behind a reCAPTCHA.

If you prefer Python, the same idea is explained step by step in the companion article: 2captcha service to solve reCAPTCHA.


Why a captcha-solving service?

reCAPTCHA is designed to block automated traffic. For legitimate automation — QA pipelines, regression testing of registration/checkout flows, monitoring, or large-scale collection of public data — that protection becomes a wall you have to get through without a human sitting at the keyboard.

A captcha-solving service like 2captcha acts as that "human in the loop." It is an AI-first service: most tasks are recognized automatically by machine-learning models, and only rare edge cases are escalated to verified human workers. You hand the service the captcha's parameters, it returns a valid answer token, and you inject that token into the target page exactly as a real browser would.

Use it responsibly. Solving captchas to bypass protections on sites you do not own, or in violation of a site's terms of service, can be illegal. Stick to systems you own, testing environments, or data you are explicitly permitted to access.


How the reCAPTCHA v2 workflow works

Regardless of the language you use, solving a reCAPTCHA v2 ("I'm not a robot") always follows the same four-step cycle:

  1. Read the captcha parameters from the page. You need two values: the site key and the page URL. The site key lives in the data-sitekey attribute of the reCAPTCHA element (or in the k parameter of the www.google.com/recaptcha/api2/anchor request). The page URL is simply the full address of the page where the captcha appears.

  2. Submit the task to 2captcha. Send the site key and page URL to the service. It queues the job and returns a captcha ID.

  3. Poll for the answer. Captchas are not solved instantly — typical solve times run from a few seconds to a couple of minutes. You poll the service with the captcha ID until it returns the answer token (commonly called the g-recaptcha-response token).

  4. Use the token on the target site. Place the token into the page's hidden g-recaptcha-response field (or pass it to the page's callback function), then submit the form. The target site validates the token with Google's reCAPTCHA API, just as it would for a real user.

The same pattern extends to reCAPTCHA v2 Invisible, reCAPTCHA v2 Callback, reCAPTCHA Enterprise, and — with a couple of extra parameters (action, score) — reCAPTCHA v3.


Two ways to integrate

You can talk to 2captcha in two ways:

Approach When to use it
Official C# library (NuGet 2captcha-csharp) Recommended for almost everything. It handles submission, polling, timeouts, retries, proxies, error types, and every captcha type with a clean async API.
Raw HTTP API Useful when you cannot add a dependency, want full control, or are learning how the service works under the hood.

2captcha currently exposes two HTTP API surfaces: the long-standing endpoints (https://2captcha.com/in.php and https://2captcha.com/res.php) and a newer JSON task API (https://api.2captcha.com/createTask and https://api.2captcha.com/getTaskResult). The official library wraps these for you. Both support HTTP and HTTPS — always prefer HTTPS so your API key is never sent in clear text.


Option A — The official C# library (recommended)

Install the package:

bash
dotnet add package 2captcha-csharp

Solving a reCAPTCHA v2 is then just a few lines. The library submits the task, polls for the result, and gives you the token back through captcha.Code:

c#
using System;
using TwoCaptcha.Captcha;

class Program
{
    static async Task Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.WriteLine("Usage: app <2captcha_api_key>");
            return;
        }

        var solver = new TwoCaptcha(args[0]);

        var captcha = new ReCaptcha();
        captcha.SetSiteKey("6Lf5CQkTAAAAAKA-kgNm9mV6sgqpGmRmRMFJYMz8");
        captcha.SetUrl("https://example.com/page-with-recaptcha");

        try
        {
            await solver.Solve(captcha);
            Console.WriteLine("Token: " + captcha.Code);
            // captcha.Code now holds the g-recaptcha-response token —
            // submit it to the target site's form.
        }
        catch (Exception e)
        {
            Console.WriteLine("Solving failed: " + e.Message);
        }
    }
}

Useful configuration knobs on the TwoCaptcha instance:

c#
var solver = new TwoCaptcha("YOUR_API_KEY");
solver.DefaultTimeout    = 120;  // seconds to wait for non-reCAPTCHA results
solver.RecaptchaTimeout  = 600;  // seconds to wait for reCAPTCHA results
solver.PollingInterval   = 10;   // seconds between polls (keep >= 5)

The same ReCaptcha object handles the variations through extra setters:

c#
captcha.SetInvisible(true);    // reCAPTCHA v2 Invisible
captcha.SetEnterprise(true);   // reCAPTCHA Enterprise
captcha.SetVersion("v3");      // reCAPTCHA v3
captcha.SetAction("verify");   // v3 action name
captcha.SetScore(0.7);         // v3 minimum score
captcha.SetProxy("HTTPS", "login:password@IP_address:PORT");

Other handy methods the library exposes: check your balance with await solver.Balance(), and report a good or bad answer (which refunds bad tokens) with await solver.Report(captcha.Id, true) or await solver.Report(captcha.Id, false).


Option B — Raw HTTP API with HttpClient

If you would rather not pull in a dependency, here is a modern, self-contained implementation using HttpClient, async/await, and System.Text.Json. It replaces the obsolete HttpWebRequest/WebClient style and talks to the legacy in.php/res.php endpoints in JSON mode (json=1).

c#
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

public sealed class TwoCaptchaClient
{
    private static readonly HttpClient Http = new HttpClient();
    private readonly string _apiKey;

    public TwoCaptchaClient(string apiKey) => _apiKey = apiKey;

    // Step 1: submit the reCAPTCHA task, receive a captcha id.
    public async Task<string> SubmitAsync(string siteKey, string pageUrl)
    {
        var url = "https://2captcha.com/in.php" +
                  $"?key={_apiKey}" +
                  "&method=userrecaptcha" +
                  $"&googlekey={siteKey}" +
                  $"&pageurl={Uri.EscapeDataString(pageUrl)}" +
                  "&json=1";

        var json = await Http.GetStringAsync(url);
        var root = JsonDocument.Parse(json).RootElement;

        if (root.GetProperty("status").GetInt32() != 1)
            throw new Exception("Submit error: " + root.GetProperty("request").GetString());

        return root.GetProperty("request").GetString(); // the captcha id
    }

    // Step 2: poll until the token is ready (or we give up).
    public async Task<string> GetTokenAsync(string captchaId, int maxAttempts = 24, int delayMs = 5000)
    {
        var url = "https://2captcha.com/res.php" +
                  $"?key={_apiKey}&action=get&id={captchaId}&json=1";

        for (var attempt = 1; attempt <= maxAttempts; attempt++)
        {
            await Task.Delay(delayMs);

            var json = await Http.GetStringAsync(url);
            var root = JsonDocument.Parse(json).RootElement;
            var request = root.GetProperty("request").GetString();

            if (root.GetProperty("status").GetInt32() == 1)
                return request; // solved — this is the g-recaptcha-response token

            if (request != "CAPCHA_NOT_READY")
                throw new Exception("Polling error: " + request);

            Console.WriteLine($"Still solving... ({attempt * delayMs / 1000}s elapsed)");
        }

        throw new TimeoutException("Captcha was not solved in time.");
    }

    // Step 4: submit the solved token to the target form.
    public async Task<string> SubmitFormAsync(string pageUrl, string token)
    {
        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("submit", "submit"),
            new KeyValuePair<string, string>("g-recaptcha-response", token),
        });

        var response = await Http.PostAsync(pageUrl, form);
        return await response.Content.ReadAsStringAsync();
    }
}

Putting the raw client together

c#
class Program
{
    static async Task Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.WriteLine("Usage: app <2captcha_api_key>");
            return;
        }

        var client  = new TwoCaptchaClient(args[0]);
        var siteKey = "6Lf5CQkTAAAAAKA-kgNm9mV6sgqpGmRmRMFJYMz8";
        var pageUrl = "https://example.com/page-with-recaptcha";

        try
        {
            // 1. submit
            var captchaId = await client.SubmitAsync(siteKey, pageUrl);
            Console.WriteLine("Captcha id: " + captchaId);

            // 2. & 3. poll for the token
            var token = await client.GetTokenAsync(captchaId);
            Console.WriteLine("g-recaptcha-response token: " + token);

            // 4. submit the form with the token
            var result = await client.SubmitFormAsync(pageUrl, token);
            Console.WriteLine("Target site response: " + result);
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: " + e.Message);
        }
    }
}

Practical tips

  • Always escape the page URL before putting it in a query string (Uri.EscapeDataString). A raw &, ?, or = in the URL will otherwise corrupt your request.
  • Mind the page URL exactly. The full URL of the page hosting the reCAPTCHA must be sent; a mismatched URL is one of the most common reasons a token is rejected by the target site.
  • Don't poll too aggressively. A polling interval below five seconds is discouraged and won't make the answer arrive faster.
  • Reuse a single HttpClient. Creating a new one per request can exhaust sockets. A static instance (as above) is the recommended pattern.
  • Handle the IP-matching case. For some Google properties the token must be solved from a matching IP. In that situation, supply a proxy (SetProxy) so the captcha is solved through the same address you'll submit the form from.
  • Report bad tokens. If a token is rejected, report it back to 2captcha — bad answers are refunded, and the report improves future solving for your account.

Summary

Integrating 2captcha into C# comes down to the same four steps every time: read the site key and page URL, submit the task, poll for the token, and feed the token back to the target form. The official 2captcha-csharp NuGet package is the path of least resistance for production code, while a lightweight HttpClient implementation gives you full control and a clear view of what the service is doing underneath. Either way, keep your usage within what you're legally permitted to automate, and you'll have a reliable reCAPTCHA flow.

For the Python equivalent and more background on the service, see 2captcha service to solve reCAPTCHA.