Skip to content

Cloudflare Turnstile solver · Java

Solve Cloudflare Turnstile in Java

Java 17's HttpClient and a JSON library are all it takes: create a Turnstile task, poll for the token and read the API's error code when a task fails. There is no Java SDK to add.

No SDK needed: plain JSON over HTTPS

The steps in Java

  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 Java 17 or later and Jackson (com.fasterxml.jackson.core:jackson-databind).
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class SolveTurnstile {
    private static final String API = System.getenv("ZEROCAPTCHA_API");
    private static final String KEY = System.getenv("ZEROCAPTCHA_KEY");
    private static final HttpClient HTTP = HttpClient.newHttpClient();
    private static final ObjectMapper JSON = new ObjectMapper();

    static JsonNode call(String method, Map<String, Object> body, String idempotencyKey) throws Exception {
        Map<String, Object> payload = new HashMap<>(body);
        payload.put("clientKey", KEY);
        HttpRequest.Builder request = HttpRequest.newBuilder(URI.create(API + "/" + method))
                .timeout(Duration.ofSeconds(15))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(payload)));
        if (idempotencyKey != null) {
            // One key per task: sending the create again with it returns the same task.
            request.header("Idempotency-Key", idempotencyKey);
        }
        HttpResponse<String> response = HTTP.send(request.build(), HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != 200) {
            throw new IllegalStateException(method + ": HTTP " + response.statusCode() + ", try again later");
        }
        JsonNode reply = JSON.readTree(response.body());
        if (reply.path("errorId").asInt() != 0) {
            // A refused create, or a task that failed or expired: errorCode says which. Neither is charged.
            throw new IllegalStateException(method + ": " + reply.path("errorCode").asText()
                    + ": " + reply.path("errorDescription").asText());
        }
        return reply;
    }

    static String solveTurnstile(Map<String, Object> task) throws Exception {
        Map<String, Object> body = new HashMap<>(Map.of("task", task));
        // Optional: where to POST the result when the task ends, instead of polling.
        // body.put("callbackUrl", "https://hooks.example.com/zerocaptcha");
        JsonNode created = call("createTask", body, UUID.randomUUID().toString());
        String taskId = created.path("taskId").asText();
        Instant stop = Instant.now().plusSeconds(180);
        while (Instant.now().isBefore(stop)) {
            Thread.sleep(2000);
            JsonNode result = call("getTaskResult", Map.of("taskId", taskId), null);
            if ("ready".equals(result.path("status").asText())) {
                return result.path("solution").path("token").asText();
            }
        }
        throw new IllegalStateException("Task " + taskId + " is still running");
    }

    public static void main(String[] args) throws Exception {
        Map<String, Object> task = new HashMap<>();
        // Or "TurnstileTask", to solve through your own proxy, with "proxy" below.
        task.put("type", "TurnstileTaskProxyless");
        task.put("websiteURL", "https://shop.example.com/login"); // the page with the widget
        task.put("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.
        task.put("metadata", Map.of("action", "login", "cdata", "session-7f3a9c2e"));
        // task.put("proxy", "http://user:pass@proxy.example.net:8080"); // TurnstileTask only
        System.out.println(solveTurnstile(task));
    }
}

Good to know

Read next

Java questions

Is there a Java SDK?

No. The API is plain JSON over HTTPS, so java.net.http and a JSON library 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.