{"id":24965,"date":"2026-08-06T10:16:46","date_gmt":"2026-08-06T10:16:46","guid":{"rendered":"https:\/\/capskip.com\/?p=24965"},"modified":"2026-08-06T10:16:46","modified_gmt":"2026-08-06T10:16:46","slug":"recaptcha-v2-csharp","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/recaptcha-v2-csharp\/","title":{"rendered":"\u5982\u4f55\u5728 C# \u4e2d\u8bc6\u522b reCAPTCHA v2\uff0c\u5305\u62ec\u9690\u5f62"},"content":{"rendered":"<p>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.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Setup<\/h2>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\"># Targets .NET Standard 2.0, so Framework 4.6.1+ and .NET 6-9 all work.\ndotnet add package CapSkip<\/pre>\n<p>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:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using CapSkip;\n\nvar solver = new CapSkipClient(\n    apiKey: &quot;capskip&quot;,        \/\/ any string when key validation is off\n    host: &quot;127.0.0.1&quot;,\n    port: 8080,\n    recaptchaTimeout: 300);   \/\/ seconds<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The three variants<\/h2>\n<table>\n<thead>\n<tr>\n<th>Variant<\/th>\n<th>What changes<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Checkbox<\/td>\n<td>Nothing. Sitekey and URL only<\/td>\n<\/tr>\n<tr>\n<td>Invisible<\/td>\n<td>Add <code>[\"invisible\"] = 1<\/code><\/td>\n<\/tr>\n<tr>\n<td>Enterprise<\/td>\n<td>Add <code>[\"enterprise\"] = 1<\/code><\/td>\n<\/tr>\n<tr>\n<td>Invisible Enterprise<\/td>\n<td>Both keys together<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Checkbox first, since everything else builds on it:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var result = await solver.RecaptchaAsync(\n    &quot;6Lc...YOUR_SITEKEY&quot;,               \/\/ the data-sitekey attribute\n    &quot;https:\/\/example.com\/login&quot;);       \/\/ the page the widget sits on\n\nConsole.WriteLine(result.Code);         \/\/ g-recaptcha-response token<\/pre>\n<p>Invisible looks identical apart from the options bag:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using System.Collections.Generic;\n\nvar result = await solver.RecaptchaAsync(sitekey, pageUrl,\n    new Dictionary&lt;string, object?&gt;\n    {\n        [&quot;invisible&quot;] = 1,\n    });<\/pre>\n<p>And Enterprise, which is an orthogonal flag rather than a different product:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var result = await solver.RecaptchaAsync(sitekey, pageUrl,\n    new Dictionary&lt;string, object?&gt;\n    {\n        [&quot;enterprise&quot;] = 1,\n        [&quot;invisible&quot;]  = 1,    \/\/ combine freely if the widget is both\n    });<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Finding the sitekey<\/h2>\n<p>For checkbox widgets it is the <code>data-sitekey<\/code> attribute on the container div. For Invisible there may be no visible container, in which case look for the sitekey in the <code>grecaptcha.render<\/code> call or the reCAPTCHA script URL. Either way it is public, always starts with <code>6L<\/code>, and is safe to hard-code.<\/p>\n<p>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.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Submitting the token<\/h2>\n<p>The token goes into a field named <code>g-recaptcha-response<\/code>, exactly as the browser would have sent it:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using System.Net.Http;\n\nusing var http = new HttpClient();\n\nvar form = new FormUrlEncodedContent(new[]\n{\n    new KeyValuePair&lt;string, string&gt;(&quot;g-recaptcha-response&quot;, result.Code),\n    new KeyValuePair&lt;string, string&gt;(&quot;username&quot;, &quot;...&quot;),\n    new KeyValuePair&lt;string, string&gt;(&quot;password&quot;, &quot;...&quot;),\n});\n\nvar response = await http.PostAsync(&quot;https:\/\/example.com\/login&quot;, form);<\/pre>\n<p>Tokens are short-lived, generally around two minutes, and single use. Solve as late as you can in the flow rather than up front.<\/p>\n<p>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 <a href=\"https:\/\/capskip.com\/recaptcha-v2-callback-solver\/\">reCAPTCHA v2 callback solver<\/a> page. The solve itself is unchanged.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Errors worth catching<\/h2>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using System;\nusing CapSkip;\n\ntry\n{\n    var result = await solver.RecaptchaAsync(sitekey, pageUrl, options);\n}\ncatch (CapSkip.ValidationException) { \/* missing sitekey or url *\/ }\ncatch (NetworkException)            { \/* CapSkip is not running *\/ }\ncatch (ApiException)                { \/* bad sitekey or pageurl *\/ }\ncatch (CapSkip.TimeoutException)    { \/* exceeded recaptchaTimeout *\/ }\ncatch (CapSkipError)                { \/* anything else *\/ }<\/pre>\n<p><code>ValidationException<\/code> and <code>TimeoutException<\/code> exist in both <code>System<\/code> and <code>CapSkip<\/code>. With both namespaces imported an unqualified catch binds to the <code>System<\/code> type and silently never fires. Qualify them as above, or catch the base <code>CapSkipError<\/code> and inspect it.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Concurrency and proxies<\/h2>\n<p>Every method returns a <code>Task<\/code>, so ordinary .NET concurrency works:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var results = await Task.WhenAll(\n    solver.RecaptchaAsync(sitekeyA, &quot;https:\/\/a.example.com&quot;),\n    solver.RecaptchaAsync(sitekeyB, &quot;https:\/\/b.example.com&quot;));<\/pre>\n<p><code>AsyncCapSkip<\/code> is exported too, but in .NET it is only an alias of <code>CapSkipClient<\/code>. It exists so code ported from the Python SDK still compiles, and switching to it changes nothing.<\/p>\n<p>If the site is geo-sensitive, solve through the same egress you will submit from:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var result = await solver.RecaptchaAsync(sitekey, pageUrl,\n    new Dictionary&lt;string, object?&gt;\n    {\n        [&quot;proxy&quot;] = new Proxy(&quot;HTTPS&quot;, &quot;user:pass@1.2.3.4:3128&quot;),\n    });<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Frequently asked questions<\/h2>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">How do I know if a widget is Invisible or Enterprise?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Invisible has no checkbox and usually fires on submit, often with a badge in the corner. Enterprise loads from <code>enterprise.js<\/code> rather than <code>api.js<\/code>, 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.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Can I reuse a token?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">No. Tokens are single use and expire in roughly two minutes. Solve immediately before submitting rather than building a pool.<\/p>\n<\/details>\n<details style=\"border:1px solid #e2e5ee;border-radius:10px;padding:14px 18px;margin:0 0 12px;\">\n<summary style=\"cursor:pointer;\">\n<h3 style=\"font-size:1.15rem;line-height:1.4;display:inline;margin:0;\">Does this work on .NET Framework?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">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 <code>.GetAwaiter().GetResult()<\/code>, though awaiting properly is better.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Summary<\/h2>\n<p>One method covers all three variants. Add <code>invisible<\/code> or <code>enterprise<\/code> as options, use the page the widget renders on, put the result in <code>g-recaptcha-response<\/code>, and catch <code>CapSkipError<\/code> to avoid the namespace collision.<\/p>\n<p>The full .NET surface is on the <a href=\"https:\/\/capskip.com\/csharp-captcha-solver\/\">C# CAPTCHA solver<\/a> page, other languages are covered by the <a href=\"https:\/\/capskip.com\/recaptcha-v2-solver\/\">reCAPTCHA v2 solver<\/a> page, and you can experiment on our <a href=\"https:\/\/capskip.com\/captcha-demo\/recaptcha-v2\/\">live v2 demo<\/a>. CapSkip is a <a href=\"https:\/\/capskip.com\/\">local captcha solver<\/a>, so volume costs nothing per solve.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u590d\u9009\u6846\u3001\u9690\u5f62\u4e0e Enterprise \u5728 C# \u4e2d\u90fd\u662f\u540c\u4e00\u4e2a\u8c03\u7528\u3001\u4e0d\u540c\u9009\u9879\u3002\u8fd9\u91cc\u7ed9\u51fa\u6bcf\u4e00\u79cd\u6240\u9700\u7684\u9009\u9879\uff0c\u4ee5\u53ca token \u7684\u53bb\u5411\u3002<\/p>","protected":false},"author":1,"featured_media":24964,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve reCAPTCHA v2 in C# (.NET) | CapSkip","rank_math_description":"One method covers reCAPTCHA v2 checkbox, Invisible and Enterprise in C#. Here is the option each variant needs, and how to submit the token you get back.","rank_math_focus_keyword":"solve recaptcha v2 in c#","footnotes":""},"categories":[71],"tags":[],"class_list":["post-24965","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-google-recaptcha"],"_links":{"self":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24965","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/comments?post=24965"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24965\/revisions"}],"predecessor-version":[{"id":24988,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24965\/revisions\/24988"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/24964"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=24965"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=24965"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=24965"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}