Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha and image captchas from C#.
Official .NET client for CapSkip, a local captcha solver that runs on your own machine. Licensed once, not billed per solve.
dotnet add package CapSkipTargets .NET Standard 2.0, so it runs on .NET 6/7/8/9+, .NET Core 2.0+, and .NET Framework 4.6.1+.
CapSkip is a desktop app. It does the solving on your machine and exposes the standard captcha-solver HTTP API — the same in.php / res.php endpoints every 2captcha-compatible client already speaks — on 127.0.0.1:8080.
This SDK is a thin wrapper over that API, with the method names you would expect: NormalAsync(), RecaptchaAsync(), TurnstileAsync(), GeetestAsync(), AltchaAsync(), CapyAsync(), CaptchaFoxAsync(), FriendlyCaptchaAsync(). Nothing leaves your network, and there is no credit balance to keep an eye on.
| Captcha | Method |
|---|---|
| Image captcha solver (distorted text / OCR) | solver.NormalAsync(file) |
| reCAPTCHA v2 solver (checkbox) | solver.RecaptchaAsync(sitekey, url) |
| reCAPTCHA v2 invisible solver | RecaptchaAsync(sitekey, url, new() { ["invisible"] = 1 }) |
| reCAPTCHA Enterprise solver | RecaptchaAsync(sitekey, url, new() { ["enterprise"] = 1 }) |
| reCAPTCHA v3 solver | RecaptchaAsync(sitekey, url, new() { ["version"] = "v3", ["action"] = "submit" }) |
| reCAPTCHA v3 Enterprise | RecaptchaAsync(sitekey, url, new() { ["version"] = "v3", ["enterprise"] = 1 }) |
| Cloudflare Turnstile solver (widget) | solver.TurnstileAsync(sitekey, url) |
| Cloudflare Turnstile (challenge page) | TurnstileAsync(sitekey, url, new() { ["data"] = ..., ["pagedata"] = ... }) |
| GeeTest v3 solver (slide puzzle) | solver.GeetestAsync(gt, challenge, url) |
| ALTCHA solver (proof-of-work) | solver.AltchaAsync(url, options) |
| Capy Puzzle solver (slide puzzle) | solver.CapyAsync(sitekey, url) |
| CaptchaFox solver (widget) | solver.CaptchaFoxAsync(sitekey, url) |
| Friendly Captcha solver (proof-of-work) | FriendlyCaptchaAsync(sitekey, url, new() { ["version"] = "v2" }) |
The options argument is a Dictionary<string, object?> — the table shortens it to new() { ... } to stay readable; full signatures are in the API Reference.
hCaptcha and FunCaptcha/Arkose are not supported. hCaptcha is the one people misidentify most often, since it also puts a data-sitekey on the widget — check for class="h-captcha" or a js.hcaptcha.com script before reaching for RecaptchaAsync().
Point the SDK at the live captcha demo pages to sanity-check your setup against real widgets.
Download and run the CapSkip desktop app — CapSkipInstaller.msi. Leave it running in the background.
In CapSkip settings, note:
- API port (default:
8080) - API key (optional — if validation is disabled, any string works)
dotnet add package CapSkipusing CapSkip;
var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
var result = await solver.RecaptchaAsync(
"YOUR_SITEKEY",
"https://example.com/page-with-recaptcha");
Console.WriteLine(result.Code); // g-recaptcha-response tokenPrerequisite: CapSkip must be running before you call the SDK. If you see a connection error, see Troubleshooting.
Every solve method is asynchronous — await it (or call .GetAwaiter().GetResult() from synchronous code).
Cloud captcha APIs charge per solve, which turns a retry loop into an expense and routes every page URL and sitekey you touch through someone else's queue.
CapSkip flips that around:
- Unlimited solving — one license, no per-captcha charge, no balance to top up
- Runs on
127.0.0.1— the SDK never talks to a third-party server - No per-key rate limit — throughput is whatever your machine can manage
- Fast — image captchas come back in well under a second; a typical reCAPTCHA v2 lands in 30–45 seconds
CapSkip answers on the same in.php / res.php endpoints, so it works as a 2captcha API alternative: an existing integration usually needs nothing more than its host pointed at 127.0.0.1:8080. The migration notes cover the details, if you would rather keep your current client library than switch to this one.
| Guide | Description |
|---|---|
| Tutorial | Complete walkthrough of every captcha type |
| Getting Started | Full setup: CapSkip app, SDK install, first program |
| API Reference | All classes, methods, parameters, and return values |
| Examples | Ready-to-run samples for every captcha type |
| Troubleshooting | Connection errors, timeouts, proxy issues |
| Contributing | Development setup, tests, pull requests |
| Changelog | Release history |
using CapSkip;
var solver = new CapSkipClient(
apiKey: "capskip", // your CapSkip API key (or any string if validation is off)
host: "127.0.0.1", // CapSkip host
port: 8080, // CapSkip port from app settings
defaultTimeout: 120, // seconds — image captcha polling timeout
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
// Friendly Captcha polling timeout
// (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
pollingInterval: 5); // max seconds between res.php polls (starts at 0.25s, backs off to this)Use environment variables in production:
# Linux / macOS
export CAPSKIP_API_KEY="your-key"
export CAPSKIP_HOST="127.0.0.1"
export CAPSKIP_PORT="8080"# Windows PowerShell
$env:CAPSKIP_API_KEY = "your-key"
$env:CAPSKIP_HOST = "127.0.0.1"
$env:CAPSKIP_PORT = "8080"using CapSkip;
var solver = new CapSkipClient(
apiKey: Environment.GetEnvironmentVariable("CAPSKIP_API_KEY") ?? "capskip",
host: Environment.GetEnvironmentVariable("CAPSKIP_HOST") ?? "127.0.0.1",
port: int.TryParse(Environment.GetEnvironmentVariable("CAPSKIP_PORT"), out var p) ? p : 8080);await solver.NormalAsync("captcha.png");
await solver.NormalAsync("https://example.com/captcha.jpg");
await solver.NormalAsync("data:image/png;base64,iVBORw0KGgo...");
// result.Code holds the recognized text// reCAPTCHA v2
var v2 = await solver.RecaptchaAsync("...", "https://example.com");
// reCAPTCHA v3
var v3 = await solver.RecaptchaAsync("...", "https://example.com", new Dictionary<string, object?>
{
["version"] = "v3",
["action"] = "submit",
["score"] = 0.7,
});var result = await solver.TurnstileAsync("0x4AAAAAAA...", "https://example.com");gt is static per site, but challenge is single-use and expires in about a
minute — fetch a fresh pair right before solving.
var result = await solver.GeetestAsync(
"81388ea1fc187e0c335c0a8907ff2625",
"7cf6a8b1a2c34d5e6f7089abcdef0123",
"https://example.com/login");
// Post these back exactly as the site's own front-end would
result.Challenge; result.Validate; result.Seccode;ALTCHA is proof-of-work, not recognition — there is nothing to read, so a solve is deterministic and takes milliseconds. Give CapSkip the endpoint that serves the challenge, or the challenge document itself.
var result = await solver.AltchaAsync(
"https://example.com/signup",
new Dictionary<string, object?>
{
["challenge_url"] = "https://example.com/captcha/api/altcha/challenge",
});
// Post this back in the form field the widget uses, named `altcha`
result.Token;Challenges expire fast — some sites inside two minutes — so fetch one immediately before solving and submit the token promptly.
A Capy solution is not a token. It is three values that together go into the target form, so post all three back exactly as returned.
var result = await solver.CapyAsync(
"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
"https://example.com/login");
// capy_captchakey, capy_challengekey and capy_answer respectively
result.CaptchaKey; result.ChallengeKey; result.Answer;Answer is the drag path the widget would have recorded — submit it verbatim,
without trimming or re-encoding it. The challenge key is single-use and
short-lived, so submit promptly rather than caching the three values.
Pass api_server when the widget script points somewhere other than
https://jp.api.capy.me. Only the puzzle family is solved; ["version"] = "avatar" is refused rather than answered with a puzzle answer the site would
reject.
var result = await solver.CaptchaFoxAsync(
"sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G",
"https://example.com/signup");
// Post this back in the form field the widget uses, named `cf-captcha-response`
result.Token;
// The UA the token was minted under — submit under this one, not your own
result.UserAgent;url has to be the page the widget actually runs on: CaptchaFox checks it
against the domains the key is registered for and refuses a mismatch
permanently, not intermittently.
Pass api_server only when the page does not load the default widget. A page
loading the MAM package expects a MAM_ prefixed token, and sending the wrong
source still succeeds — it just returns a token the site will not accept, which
reads as a silent verification failure rather than an error.
Two different protocols ship under this name and a sitekey does not tell you which one a site uses, so say which — or send the widget script and let CapSkip read it off the build the site actually loads.
var result = await solver.FriendlyCaptchaAsync(
"FCMGEMUD2M567T8G",
"https://example.com/signup",
new Dictionary<string, object?> { ["version"] = "v2" });
// v1 → frc-captcha-solution, v2 → frc-captcha-response
result.Token;// Or let the script URL decide the version
var result = await solver.FriendlyCaptchaAsync(
"FCMGEMUD2M567T8G",
"https://example.com/signup",
new Dictionary<string, object?>
{
["module_script"] = "https://cdn.example.com/@friendlycaptcha/sdk@0.1.6/site.min.js",
});Solving the wrong version returns a well-formed token the target site rejects,
with nothing to indicate the version was the problem. With neither version nor
a script URL, v1 is assumed. Pass ["api_server"] = "eu" for a sitekey on the EU
data-residency tenant. A v2 token is roughly six kilobytes, so size whatever
carries it accordingly.
// Proxy is not supported for image captcha
await solver.RecaptchaAsync("...", "https://example.com", new Dictionary<string, object?>
{
["proxy"] = new Proxy("HTTPS", "user:pass@1.2.3.4:3128"),
});
await solver.TurnstileAsync("...", "https://example.com", new Dictionary<string, object?>
{
["proxy"] = new Proxy("HTTP", "1.2.3.4:3128"),
});using CapSkip;
var solver = new CapSkipClient();
var results = await Task.WhenAll(
solver.RecaptchaAsync("...", "https://a.com"),
solver.TurnstileAsync("...", "https://b.com"));
Console.WriteLine($"{results[0].Code} {results[1].Code}");
AsyncCapSkipis provided as an alias ofCapSkipClient— .NET I/O is asynchronous by nature, so every method already returns aTask. It exists for parity with the other CapSkip SDKs.
More examples: examples/
The SDK hands back a token; your existing browser tooling does the driving. The shape is the same whichever you use:
- Read the sitekey off the page (
data-sitekey, or the widget's config object). - Call the matching solve method with that sitekey and the page URL.
- Write the token into the response field and submit.
var sitekey = driver.FindElement(By.CssSelector("[data-sitekey]")).GetAttribute("data-sitekey");
var result = await solver.RecaptchaAsync(sitekey, driver.Url);
((IJavaScriptExecutor)driver).ExecuteScript(
"document.getElementById('g-recaptcha-response').value = arguments[0];", result.Code);
driver.FindElement(By.CssSelector("form")).Submit();var sitekey = await page.GetAttributeAsync("[data-sitekey]", "data-sitekey");
var result = await solver.RecaptchaAsync(sitekey, page.Url);
await page.EvaluateAsync(
"t => document.getElementById('g-recaptcha-response').value = t", result.Code);PuppeteerSharp follows the same pattern with page.EvaluateFunctionAsync. Longer walkthroughs: Selenium, Playwright and Puppeteer.
Every solve method resolves to a SolveResult:
public sealed class SolveResult
{
public string CaptchaId { get; } // internal ID from CapSkip
public string Code { get; } // solution — text for image, token for reCAPTCHA/Turnstile
public string? UserAgent { get; } // Turnstile & CaptchaFox — the UA that minted the token
public string? Challenge { get; } // GeeTest only — geetest_challenge
public string? Validate { get; } // GeeTest only — geetest_validate
public string? Seccode { get; } // GeeTest only — geetest_seccode
public string? Token { get; } // ALTCHA, CaptchaFox & Friendly Captcha — the payload to post back
public long? Number { get; } // ALTCHA only — the counter that solved it
public string? CaptchaKey { get; } // Capy only — capy_captchakey
public string? ChallengeKey { get; } // Capy only — capy_challengekey
public string? Answer { get; } // Capy only — capy_answer (the drag path)
public string? RespKey { get; } // Capy only — empty; carries nothing
}All SDK exceptions derive from CapSkipError, so you can catch that one type — or handle each kind:
using CapSkip;
try
{
var result = await solver.RecaptchaAsync("...", "...");
}
catch (ValidationException) { /* invalid parameters */ }
catch (NetworkException) { /* CapSkip not running, or captcha not ready (manual polling) */ }
catch (ApiException) { /* API returned an error code */ }
catch (CapSkip.TimeoutException) { /* polling timeout exceeded */ }
catch (CapSkipError) { /* any other CapSkip failure */ }Note:
CapSkip.TimeoutExceptionandCapSkip.ValidationExceptionshare their short names with types inSystem. If you have bothusing System;andusing CapSkip;, qualify them asCapSkip.TimeoutException/CapSkip.ValidationException, or just catch the baseCapSkipError.
Install the CapSkip desktop app, dotnet add package CapSkip, then await the method that matches the widget — RecaptchaAsync(), TurnstileAsync(), GeetestAsync(), AltchaAsync(), CapyAsync(), CaptchaFoxAsync(), FriendlyCaptchaAsync() or NormalAsync(). Each returns a SolveResult whose Code is the token, or the recognized text in the case of an image captcha.
The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is bought once rather than metered per captcha, so your cost stops scaling with volume.
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
Yes — see above. The SDK never touches a browser itself, so it drops into whatever stack you already have, PuppeteerSharp and plain HttpClient included.
The package targets .NET Standard 2.0, which covers .NET 6 through 9+, .NET Core 2.0+, and .NET Framework 4.6.1+.
Google derives v3 scores from IP reputation, cookies and browsing history. A solver returns a valid token, but it cannot change how Google grades that token — score is forwarded as the target you want, not a guarantee. If a site enforces a high threshold, solve through a cleaner IP using the proxy option.
Yes. CapSkip serves the same endpoints, so you can either move to this SDK or repoint an existing 2captcha client at 127.0.0.1:8080.
For widget captchas, yes — CapSkip loads the URL you pass it. Image captchas only need the image, and that can be a local file.
git clone https://github.com/capskip/capskip-dotnet.git
cd capskip-dotnet
dotnet testThe test suite mocks the HTTP layer and spins up a local mock server — no CapSkip app or network access required. See CONTRIBUTING.md for the full development workflow.
- CapSkip — local captcha solver · download the app
- Captcha demo pages — live reCAPTCHA, Turnstile, GeeTest and image widgets
- C# captcha solver guide
- HTTP API docs
- Other clients: Python · Node.js · PHP · MCP server for AI agents
- NuGet package · report an issue
MIT — see LICENSE.