{"id":24876,"date":"2026-08-05T10:27:44","date_gmt":"2026-08-05T10:27:44","guid":{"rendered":"https:\/\/capskip.com\/?p=24876"},"modified":"2026-08-05T10:27:44","modified_gmt":"2026-08-05T10:27:44","slug":"turnstile-challenge-page-csharp","status":"publish","type":"post","link":"https:\/\/capskip.com\/zh\/turnstile-challenge-page-csharp\/","title":{"rendered":"\u5982\u4f55\u5728 C# \u4e2d\u8bc6\u522b Cloudflare Turnstile \u6311\u6218\u9875\u9762"},"content":{"rendered":"<p>Cloudflare Turnstile comes in two shapes, and the C# code for each is different. A widget embedded in a form needs nothing but a sitekey and a URL. A full-page interstitial challenge needs two extra values pulled out of the page, and it will only be accepted if you send back the user agent the solver used. Miss that last part and you get a token that looks perfect and fails validation every time.<\/p>\n<p>This guide covers both, with working .NET code.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Widget or challenge page?<\/h2>\n<p>Work out which one you are looking at before writing any code.<\/p>\n<table>\n<thead>\n<tr>\n<th><\/th>\n<th>Widget mode<\/th>\n<th>Challenge page<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>What you see<\/td>\n<td>A checkbox inside a form you can still interact with<\/td>\n<td>A full-page interstitial, usually &#8220;Checking your browser&#8221;<\/td>\n<\/tr>\n<tr>\n<td>Rest of the page<\/td>\n<td>Loads normally<\/td>\n<td>Blocked until the challenge clears<\/td>\n<\/tr>\n<tr>\n<td>Needs <code>data<\/code> \/ <code>pagedata<\/code><\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Needs the returned user agent<\/td>\n<td>No<\/td>\n<td>Yes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If you are unsure, our <a href=\"https:\/\/capskip.com\/captcha-demo\/cloudflare-turnstile\/\">live Turnstile demo page<\/a> runs the widget variant, so you can compare it against whatever you are actually hitting.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Setup<\/h2>\n<p>CapSkip runs on your own machine, so there is a local service to start before any of this works. Install the SDK from NuGet:<\/p>\n<pre data-enlighter-language=\"bash\" class=\"EnlighterJSRAW\"># .NET Standard 2.0, so this works on .NET Framework 4.6.1+,\n# .NET Core 2.0+, and .NET 6 through 9.\ndotnet add package CapSkip<\/pre>\n<p>Then point the client at the port shown in the CapSkip app:<\/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, also covers Turnstile and GeeTest<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Widget mode, the easy case<\/h2>\n<p>Two arguments and you are done:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var result = await solver.TurnstileAsync(\n    &quot;0x4AAAAAAA...&quot;,                  \/\/ sitekey from the data-sitekey attribute\n    &quot;https:\/\/example.com\/login&quot;);\n\nConsole.WriteLine(result.Code);       \/\/ cf-turnstile-response token<\/pre>\n<p>Drop <code>result.Code<\/code> into the <code>cf-turnstile-response<\/code> field and submit the form. Nothing else to do.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Challenge pages need two more values<\/h2>\n<p>An interstitial challenge carries per-request state that the token is bound to. Two pieces of it have to travel with your solve request:<\/p>\n<ul>\n<li><strong>cData<\/strong>, passed as <code>data<\/code><\/li>\n<li><strong>chlPageData<\/strong>, passed as <code>pagedata<\/code><\/li>\n<\/ul>\n<p>Both are embedded in the challenge page itself rather than in a form attribute, so you have to read the page before you can solve it. On a standard Cloudflare interstitial they are exposed on the page&#8217;s own challenge options object, alongside the sitekey.<\/p>\n<p>They are also single-use and tied to that specific page load. Fetch them, solve immediately, and do not cache them between attempts.<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using System.Collections.Generic;\nusing CapSkip;\n\nvar result = await solver.TurnstileAsync(\n    sitekey,\n    pageUrl,\n    new Dictionary&lt;string, object?&gt;\n    {\n        [&quot;data&quot;]     = cData,          \/\/ the cData value from the page\n        [&quot;pagedata&quot;] = chlPageData,    \/\/ the chlPageData value\n        [&quot;action&quot;]   = &quot;managed&quot;,       \/\/ optional, when the page declares one\n    });\n\nConsole.WriteLine(result.Code);\nConsole.WriteLine(result.UserAgent);   \/\/ you are going to need this<\/pre>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">The part everyone misses: the user agent<\/h2>\n<p>Turnstile binds the token to the browser fingerprint that produced it, and the user agent is part of that. CapSkip returns the one it used in <code>result.UserAgent<\/code>. If you then submit the token from an <code>HttpClient<\/code> sending its own default user agent, the values do not match and Cloudflare rejects a token that is otherwise completely valid.<\/p>\n<p><code>UserAgent<\/code> is populated for Turnstile only. It is null for every other CAPTCHA type, which is why this trips people up when they reuse a working reCAPTCHA helper.<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using System.Net.Http;\nusing CapSkip;\n\nvar result = await solver.TurnstileAsync(sitekey, pageUrl, options);\n\nusing var http = new HttpClient();\n\n\/\/ Send back the exact user agent the solve was performed with.\nhttp.DefaultRequestHeaders.UserAgent.ParseAdd(result.UserAgent);\n\nvar form = new FormUrlEncodedContent(new[]\n{\n    new KeyValuePair&lt;string, string&gt;(&quot;cf-turnstile-response&quot;, result.Code),\n});\n\nvar response = await http.PostAsync(pageUrl, form);<\/pre>\n<p>If your token is being rejected and you have ruled out an expired <code>cData<\/code>, this is almost always the reason.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Handling failures properly<\/h2>\n<p>Every SDK exception derives from <code>CapSkipError<\/code>, so you can catch that one type or handle each individually. There is one .NET-specific trap worth knowing about.<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">using System;\nusing CapSkip;\n\ntry\n{\n    var result = await solver.TurnstileAsync(sitekey, pageUrl, options);\n}\ncatch (CapSkip.ValidationException)   { \/* bad parameters *\/ }\ncatch (NetworkException)              { \/* CapSkip is not running *\/ }\ncatch (ApiException)                  { \/* API returned an error code *\/ }\ncatch (CapSkip.TimeoutException)      { \/* exceeded recaptchaTimeout *\/ }\ncatch (CapSkipError)                  { \/* anything else from the SDK *\/ }<\/pre>\n<p><code>TimeoutException<\/code> and <code>ValidationException<\/code> exist in both <code>System<\/code> and <code>CapSkip<\/code>. With both namespaces imported, an unqualified <code>catch (TimeoutException)<\/code> resolves to the <code>System<\/code> one and silently never fires. Qualify them as above, or just catch <code>CapSkipError<\/code> and inspect it.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Solving several at once<\/h2>\n<p>Every method returns a <code>Task<\/code>, so ordinary .NET concurrency applies:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var results = await Task.WhenAll(\n    solver.TurnstileAsync(sitekeyA, &quot;https:\/\/a.example.com&quot;),\n    solver.TurnstileAsync(sitekeyB, &quot;https:\/\/b.example.com&quot;));\n\nforeach (var r in results)\n{\n    Console.WriteLine(r.Code);\n}<\/pre>\n<p>The SDK also exports <code>AsyncCapSkip<\/code>, but in .NET it is only an alias of <code>CapSkipClient<\/code>. It exists so code ported from the Python SDK keeps compiling. Switching to it gains you nothing, because .NET I\/O is already asynchronous.<\/p>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Routing through a proxy<\/h2>\n<p>If the challenge is geo-sensitive, solve from the same network path you will submit from:<\/p>\n<pre data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">var result = await solver.TurnstileAsync(sitekey, pageUrl,\n    new Dictionary&lt;string, object?&gt;\n    {\n        [&quot;data&quot;]     = cData,\n        [&quot;pagedata&quot;] = chlPageData,\n        [&quot;proxy&quot;]    = new Proxy(&quot;HTTPS&quot;, &quot;user:pass@1.2.3.4:3128&quot;),\n    });<\/pre>\n<p>Proxies are supported for Turnstile, reCAPTCHA and GeeTest. They are not supported for image CAPTCHAs, which are solved from the image bytes and never touch the target site.<\/p>\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;\">Do I always need cData and chlPageData?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">No. Only for full-page interstitial challenges. A Turnstile widget sitting inside a form needs nothing but the sitekey and the page URL, and passing empty values on a widget will make the solve fail rather than help.<\/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;\">My token is valid but the site still rejects it. Why?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Almost always the user agent. Turnstile ties the token to the fingerprint that produced it, so you have to submit using the value in <code>result.UserAgent<\/code>. The second most common cause is a stale <code>cData<\/code>, which is bound to a single page load.<\/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 it runs on .NET Framework 4.6.1 and newer, .NET Core 2.0+, and every modern .NET release. From synchronous code you can call <code>.GetAwaiter().GetResult()<\/code>, though awaiting properly is better.<\/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;\">How long should a Turnstile solve take?<\/h3>\n<\/summary>\n<p style=\"margin:12px 0 0;\">Usually a few seconds. The client polls internally, starting at 250ms and backing off to <code>pollingInterval<\/code>, and gives up at <code>recaptchaTimeout<\/code> which defaults to 300 seconds. If you are consistently hitting that ceiling, check that the CapSkip service is actually running rather than raising the timeout.<\/p>\n<\/details>\n<h2 style=\"font-size:1.6rem;line-height:1.35;\">Summary<\/h2>\n<p>Widget Turnstile is a two-argument call. Challenge pages need <code>data<\/code> and <code>pagedata<\/code> read fresh from the page, and the token has to be submitted with the user agent returned alongside it. Catch <code>CapSkipError<\/code> rather than fighting the namespace collision, and use a proxy when the challenge is geo-sensitive.<\/p>\n<p>The full method surface for .NET is on the <a href=\"https:\/\/capskip.com\/csharp-captcha-solver\/\">C# CAPTCHA solver<\/a> page, the parameter reference is in the <a href=\"https:\/\/capskip.com\/api-docs\/\">API documentation<\/a>, and <a href=\"https:\/\/capskip.com\/cloudflare-turnstile-solver\/\">Turnstile support<\/a> covers the other languages. CapSkip itself is an <a href=\"https:\/\/capskip.com\/\">unlimited captcha solver<\/a> that runs locally, so none of the above costs per solve.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5c0f\u7ec4\u4ef6\u5f0f Turnstile \u53ea\u9700 sitekey \u548c URL\u3002\u6311\u6218\u9875\u9762\u8fd8\u9700\u8981\u53e6\u5916\u4e24\u4e2a\u503c\u4ee5\u53ca\u8fd4\u56de\u7684 user agent\u3002\u8fd9\u91cc\u7528 C# \u8bf4\u660e\u4e24\u8005\u7684\u533a\u522b\u3002<\/p>","protected":false},"author":1,"featured_media":24875,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Solve Turnstile Challenge Pages in C# | CapSkip","rank_math_description":"A Turnstile challenge page needs cData, chlPageData and the solver's user agent, not just a sitekey. Here is the C# code, and where each value lives.","rank_math_focus_keyword":"turnstile challenge page","footnotes":""},"categories":[70],"tags":[],"class_list":["post-24876","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-captcha"],"_links":{"self":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24876","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=24876"}],"version-history":[{"count":1,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24876\/revisions"}],"predecessor-version":[{"id":24877,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/posts\/24876\/revisions\/24877"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media\/24875"}],"wp:attachment":[{"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/media?parent=24876"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/categories?post=24876"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/capskip.com\/zh\/wp-json\/wp\/v2\/tags?post=24876"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}