Skip to content

Cloudflare Turnstile solver · C#

Solve Cloudflare Turnstile in C#

In .NET, HttpClient and System.Text.Json cover it: create a Turnstile task, poll getTaskResult for the token, and surface the API's error code when a task fails. No NuGet package is needed.

No SDK needed: plain JSON over HTTPS

The steps in C#

  1. Get an API key

    Sign up with an email and a password, create your key in the dashboard and add funds in crypto, from $10.

  2. Create a task

    Send createTask with the page's URL, its Turnstile site key, and the widget's action and cData when it sets them. The price is held on your balance and a taskId comes back at once.

  3. Poll for the token

    Ask getTaskResult every two seconds until the status is ready, and stop after a deadline of your own, such as three minutes.

  4. Use the token within 300 seconds

    Send the token where the page sends it, usually the cf-turnstile-response form field. It works once, and expires 300 seconds after it was issued.

New to the API? The quickstart walks through sign-up, the key and the first task.

// Solve one Cloudflare Turnstile challenge with ZeroCaptcha and print its token.
// Needs .NET 8 or later; save as Program.cs in a console project and run: dotnet run
using System.Net.Http.Json;
using System.Text.Json;

var api = Environment.GetEnvironmentVariable("ZEROCAPTCHA_API");
var key = Environment.GetEnvironmentVariable("ZEROCAPTCHA_KEY");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };

async Task<JsonElement> Call(string method, Dictionary<string, object> body, string? idempotencyKey = null)
{
    body["clientKey"] = key!;
    using var request = new HttpRequestMessage(HttpMethod.Post, $"{api}/{method}") { Content = JsonContent.Create(body) };
    // One key per task: sending the create again with it returns the same task.
    if (idempotencyKey is not null) request.Headers.Add("Idempotency-Key", idempotencyKey);
    using var response = await http.SendAsync(request);
    if (!response.IsSuccessStatusCode)
        throw new HttpRequestException($"{method}: HTTP {(int)response.StatusCode}, try again later");
    var reply = await response.Content.ReadFromJsonAsync<JsonElement>();
    // A refused create, or a task that failed or expired: errorCode says which. Neither is charged.
    if (reply.GetProperty("errorId").GetInt32() != 0)
        throw new InvalidOperationException(
            $"{method}: {reply.GetProperty("errorCode")}: {reply.GetProperty("errorDescription")}");
    return reply;
}

async Task<string> SolveTurnstile(Dictionary<string, object> task)
{
    var body = new Dictionary<string, object> { ["task"] = task };
    // Optional: where to POST the result when the task ends, instead of polling.
    // body["callbackUrl"] = "https://hooks.example.com/zerocaptcha";
    var created = await Call("createTask", body, Guid.NewGuid().ToString());
    var taskId = created.GetProperty("taskId").GetString()!;
    var stop = DateTime.UtcNow.AddSeconds(180);
    while (DateTime.UtcNow < stop)
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        var result = await Call("getTaskResult", new() { ["taskId"] = taskId });
        if (result.GetProperty("status").GetString() == "ready")
            return result.GetProperty("solution").GetProperty("token").GetString()!;
    }
    throw new TimeoutException($"Task {taskId} is still running");
}

Console.WriteLine(await SolveTurnstile(new()
{
    // Or "TurnstileTask", to solve through your own proxy, with "proxy" below.
    ["type"] = "TurnstileTaskProxyless",
    ["websiteURL"] = "https://shop.example.com/login", // the page with the widget
    ["websiteKey"] = "0x4AAAAAAAB1cD2eF3gH4iJ5", // the widget's data-sitekey
    // The widget's action and cData, which many sites check when they verify the token: copy them
    // from its data-action and data-cdata attributes, or the action and cData options of
    // turnstile.render(). Leave out any the widget does not set.
    ["metadata"] = new Dictionary<string, string> { ["action"] = "login", ["cdata"] = "session-7f3a9c2e" },
    // ["proxy"] = "http://user:pass@proxy.example.net:8080", // TurnstileTask only
}));

Good to know

Read next

C# questions

Is there a .NET SDK?

No. The API is plain JSON over HTTPS, so HttpClient and System.Text.Json are enough, as the sample shows.

How long does a Cloudflare Turnstile token last?

A Cloudflare Turnstile token works once and expires 300 seconds after it is issued, so solve right before you submit. Every result tells you when its token expires.

What does a failed task cost?

Nothing. The price is held when you create a task and released at once if it fails or expires, and a refused task holds nothing; you pay only when a token is ready.