How to Solve reCAPTCHA v3 Enterprise in C# (.NET SDK)

Solving reCAPTCHA v3 Enterprise in C# uses the same method as every other reCAPTCHA type. There is no RecaptchaEnterpriseAsync. You call RecaptchaAsync and pass two flags: version set to v3, and enterprise set to 1. Miss either one and you get a token for a different product, which the site will reject.
Here is the full call, what the action option actually does, and how to tell which variant you’re looking at.
Version and enterprise are independent flags
This trips up almost everyone the first time. Enterprise isn’t a fourth version of reCAPTCHA. It’s a different Google product tier that runs both v2 and v3, so the two settings form a genuine 2×2.
| What the site runs | Options you pass |
|---|---|
| reCAPTCHA v2 checkbox | none |
| reCAPTCHA v2 Enterprise | enterprise = 1 |
| reCAPTCHA v3 | version = "v3" |
| reCAPTCHA v3 Enterprise | version = "v3" and enterprise = 1 |
Passing enterprise = 1 on its own gets you a v2 Enterprise solve, and it will fail on a v3 page. That’s the single most common cause of a token that comes back fine and then gets rejected.
How to tell if a page is Enterprise
Open the page source and look at which object the script calls. Standard reCAPTCHA uses grecaptcha. Enterprise uses grecaptcha.enterprise.
// Enterprise pages call grecaptcha.enterprise, not grecaptcha.
// The action name you need is right here in execute().
grecaptcha.enterprise.ready(function () {
grecaptcha.enterprise.execute("YOUR_SITEKEY", { action: "login" })
.then(function (token) {
// token gets posted with the form
});
});Two more tells: the script tag loads /recaptcha/enterprise.js instead of /recaptcha/api.js, and Enterprise sitekeys usually start with 6L just like standard ones, so the key itself tells you nothing. Trust the script, not the key.
Setup
CapSkip runs on your own machine, so start the app before any of this works, then install the SDK from NuGet:
# .NET Standard 2.0, so .NET Framework 4.6.1+, Core 2.0+ # and every modern .NET release are all supported. dotnet add package CapSkip
Point the client at the port shown in the app:
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; v3 rarely gets near thisThe v3 Enterprise call
Everything beyond the sitekey and URL goes in an options dictionary:
using System.Collections.Generic;
using CapSkip;
var result = await solver.RecaptchaAsync(
"YOUR_SITEKEY",
"https://example.com/page-with-recaptcha",
new Dictionary<string, object?>
{
["version"] = "v3",
["enterprise"] = 1,
["action"] = "login", // match the page exactly
});
Console.WriteLine(result.Code); // g-recaptcha-response tokenThe signature is RecaptchaAsync(string sitekey, string url, Dictionary<string, object?>? options = null). The dictionary is nullable-valued, so object? matters if your project has nullable reference types switched on.
What the action option actually does
action is v3-only. On a v2 page it does nothing.
It’s the label the page passes to grecaptcha.enterprise.execute(). Google scores each action separately, and the site’s backend usually checks that the action in the verification response matches what it expected. If the page says login and you solve with the default, your token is valid and still gets thrown out. Copy the string verbatim, including case. Google restricts actions to alphanumerics, slashes and underscores, so there’s nothing exotic to escape.
Getting it wrong is quiet. Nothing errors, because nothing went wrong on the solving side. You just get a token the endpoint refuses.
There is no minimum-score option
Worth stating plainly, because some solving APIs advertise one: you don’t ask CapSkip for a token at a particular score. The score is Google’s judgement, produced when your target site verifies the token, and no parameter on the solve request sets a floor for it. If you’ve seen a min_score field elsewhere and gone looking for the equivalent here, that’s why you can’t find it.
Submitting the token
The token goes into the field the page posts, which is normally g-recaptcha-response:
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", "demo"),
});
var response = await http.PostAsync(
"https://example.com/login", form);One thing you don’t need here: result.UserAgent. It’s populated for Turnstile only, and it’s null for every reCAPTCHA solve. If you’ve copied a helper from Turnstile code, strip that header out rather than sending a null.
reCAPTCHA tokens are also short-lived. Google expires them after two minutes, so solve at the moment you’re ready to submit, not at the top of a long workflow.
What about v2 Enterprise?
Same method, different options. Drop version and action, and add datas if Google handed the page a data-s value:
var v2 = await solver.RecaptchaAsync(sitekey, pageUrl,
new Dictionary<string, object?>
{
["enterprise"] = 1,
["datas"] = "YOUR_DATA_S_VALUE",
});data-s shows up on Google’s own properties and almost nowhere else. If you can’t find one in the page, you don’t need it.
Handling failures
Every SDK exception derives from CapSkipError. There’s one .NET trap worth knowing before you write the catch blocks.
using System;
using CapSkip;
try
{
var result = await solver.RecaptchaAsync(sitekey, pageUrl, options);
}
catch (CapSkip.ValidationException) { /* bad parameters */ }
catch (NetworkException) { /* CapSkip is not running */ }
catch (ApiException) { /* API returned an error */ }
catch (CapSkip.TimeoutException) { /* exceeded recaptchaTimeout */ }
catch (CapSkipError) { /* anything else from the SDK */ }TimeoutException and ValidationException exist in both System and CapSkip. With both namespaces imported, an unqualified catch resolves to the System type and quietly never fires. Qualify them, or catch CapSkipError and inspect it.
Common errors
| Code | Cause | Fix |
|---|---|---|
ERROR_GOOGLEKEY | Sitekey is empty or malformed | Re-read it from the live page, not from a cached copy |
ERROR_PAGEURL | URL missing or not a full absolute URL | Include the scheme, and use the page the widget is on |
ERROR_BAD_PARAMETERS | An option value is the wrong type | Check enterprise is the number 1, not the string “1” |
ERROR_CAPTCHA_UNSOLVABLE | The solve could not be completed | Confirm the version flags match the page, then add a proxy on the same network path |
The full parameter list for every type is in the API documentation.
Frequently asked questions
Is there a separate method for Enterprise?
No. RecaptchaAsync handles all four combinations. Enterprise is a flag in the options dictionary, and it works alongside version rather than replacing it.
What happens if I get the action name wrong?
You get a perfectly valid token that the site refuses. Google returns the action alongside the score during verification, and most backends compare it to what they expected. There’s no error from the solver, because nothing went wrong on the solving side.
Do I need a proxy for v3 Enterprise?
Only when the site is geo-sensitive or the score depends on the requesting IP. Pass ["proxy"] = new Proxy("HTTPS", "user:[email protected]:3128") in the same options dictionary. Proxies are supported for reCAPTCHA, Turnstile and GeeTest, but not for image CAPTCHAs.
Can I solve several pages at once?
Yes. Every method returns a Task, so await Task.WhenAll(...) is all you need. The SDK exports AsyncCapSkip too, but in .NET it’s just an alias of CapSkipClient, kept so code ported from the Python SDK still compiles. It adds nothing.
Summary
One method, two flags. Set version to v3 and enterprise to 1, copy the action off the page exactly, then submit result.Code within two minutes. There’s no score to tune, so if a token is refused, check the action and the flags first.
The rest of the .NET surface is on the C# CAPTCHA solver page, Enterprise support covers the other languages, and reCAPTCHA v3 solving goes deeper on scoring. CapSkip is a local captcha solver, so every one of these solves happens on your own machine.
