How to Solve reCAPTCHA v2 in C#, Including Invisible

solve recaptcha v2 in c# - How to Solve reCAPTCHA v2 in C#, Including Invisible

reCAPTCHA v2 comes in three flavours and people often assume each needs its own integration. In C# they are all the same method call. Checkbox is the bare call, Invisible adds one option, Enterprise adds another, and the two can be combined. Here is the whole surface, plus what to do with the token afterwards.

Setup

# Targets .NET Standard 2.0, so Framework 4.6.1+ and .NET 6-9 all work.
dotnet add package CapSkip

CapSkip solves locally, so the desktop app has to be running before any call succeeds. Point the client at the port shown in its settings:

using CapSkip;

var solver = new CapSkipClient(
    apiKey: "capskip",        // any string when key validation is off
    host: "127.0.0.1",
    port: 8080,
    recaptchaTimeout: 300);   // seconds

The three variants

VariantWhat changes
CheckboxNothing. Sitekey and URL only
InvisibleAdd ["invisible"] = 1
EnterpriseAdd ["enterprise"] = 1
Invisible EnterpriseBoth keys together

Checkbox first, since everything else builds on it:

var result = await solver.RecaptchaAsync(
    "6Lc...YOUR_SITEKEY",               // the data-sitekey attribute
    "https://example.com/login");       // the page the widget sits on

Console.WriteLine(result.Code);         // g-recaptcha-response token

Invisible looks identical apart from the options bag:

using System.Collections.Generic;

var result = await solver.RecaptchaAsync(sitekey, pageUrl,
    new Dictionary<string, object?>
    {
        ["invisible"] = 1,
    });

And Enterprise, which is an orthogonal flag rather than a different product:

var result = await solver.RecaptchaAsync(sitekey, pageUrl,
    new Dictionary<string, object?>
    {
        ["enterprise"] = 1,
        ["invisible"]  = 1,    // combine freely if the widget is both
    });

Finding the sitekey

For checkbox widgets it is the data-sitekey attribute on the container div. For Invisible there may be no visible container, in which case look for the sitekey in the grecaptcha.render call or the reCAPTCHA script URL. Either way it is public, always starts with 6L, and is safe to hard-code.

The page URL matters more than people expect. It has to be the page the widget actually renders on, not your form handler and not a redirect target. A mismatch produces a token that validates as invalid.

Submitting the token

The token goes into a field named g-recaptcha-response, exactly as the browser would have sent it:

using System.Net.Http;

using var http = new HttpClient();

var form = new FormUrlEncodedContent(new[]
{
    new KeyValuePair<string, string>("g-recaptcha-response", result.Code),
    new KeyValuePair<string, string>("username", "..."),
    new KeyValuePair<string, string>("password", "..."),
});

var response = await http.PostAsync("https://example.com/login", form);

Tokens are short-lived, generally around two minutes, and single use. Solve as late as you can in the flow rather than up front.

Some sites never render a form field at all and instead pass the token straight to a JavaScript callback. That is a different submission shape and is covered on our reCAPTCHA v2 callback solver page. The solve itself is unchanged.

Errors worth catching

using System;
using CapSkip;

try
{
    var result = await solver.RecaptchaAsync(sitekey, pageUrl, options);
}
catch (CapSkip.ValidationException) { /* missing sitekey or url */ }
catch (NetworkException)            { /* CapSkip is not running */ }
catch (ApiException)                { /* bad sitekey or pageurl */ }
catch (CapSkip.TimeoutException)    { /* exceeded recaptchaTimeout */ }
catch (CapSkipError)                { /* anything else */ }

ValidationException and TimeoutException exist in both System and CapSkip. With both namespaces imported an unqualified catch binds to the System type and silently never fires. Qualify them as above, or catch the base CapSkipError and inspect it.

Concurrency and proxies

Every method returns a Task, so ordinary .NET concurrency works:

var results = await Task.WhenAll(
    solver.RecaptchaAsync(sitekeyA, "https://a.example.com"),
    solver.RecaptchaAsync(sitekeyB, "https://b.example.com"));

AsyncCapSkip is exported too, but in .NET it is only an alias of CapSkipClient. It exists so code ported from the Python SDK still compiles, and switching to it changes nothing.

If the site is geo-sensitive, solve through the same egress you will submit from:

var result = await solver.RecaptchaAsync(sitekey, pageUrl,
    new Dictionary<string, object?>
    {
        ["proxy"] = new Proxy("HTTPS", "user:[email protected]:3128"),
    });

Frequently asked questions

How do I know if a widget is Invisible or Enterprise?

Invisible has no checkbox and usually fires on submit, often with a badge in the corner. Enterprise loads from enterprise.js rather than api.js, so check the reCAPTCHA script tag. If you guess wrong the solve fails rather than silently returning a bad token, so it is cheap to test both.

Can I reuse a token?

No. Tokens are single use and expire in roughly two minutes. Solve immediately before submitting rather than building a pool.

Does this work on .NET Framework?

Yes. The package targets .NET Standard 2.0, so Framework 4.6.1 and newer work alongside every modern .NET release. From synchronous code you can call .GetAwaiter().GetResult(), though awaiting properly is better.

Summary

One method covers all three variants. Add invisible or enterprise as options, use the page the widget renders on, put the result in g-recaptcha-response, and catch CapSkipError to avoid the namespace collision.

The full .NET surface is on the C# CAPTCHA solver page, other languages are covered by the reCAPTCHA v2 solver page, and you can experiment on our live v2 demo. CapSkip is a local captcha solver, so volume costs nothing per solve.