Skip to content

Cloudflare Turnstile solver · PHP

Solve Cloudflare Turnstile in PHP

There is no PHP package to install: the API is plain JSON over HTTPS, so PHP's curl extension is all you need. The program below creates a task, polls for its token and stops with the API's own error code when something fails.

No SDK needed: plain JSON over HTTPS

The steps in PHP

  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.

<?php
// Solve one Cloudflare Turnstile challenge with ZeroCaptcha and print its token.
// Needs PHP 8.1 or later with the curl and json extensions:
//   ZEROCAPTCHA_API=https://api.zerocaptcha.io ZEROCAPTCHA_KEY=zc_live_... php solve.php

function zerocaptcha(string $method, array $body, array $headers = []): array
{
    $curl = curl_init(getenv('ZEROCAPTCHA_API') . '/' . $method);
    curl_setopt_array($curl, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => array_merge(['Content-Type: application/json'], $headers),
        CURLOPT_POSTFIELDS => json_encode(['clientKey' => getenv('ZEROCAPTCHA_KEY')] + $body),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
    ]);
    $text = curl_exec($curl);
    $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    curl_close($curl);
    if ($text === false || $status !== 200) {
        throw new RuntimeException("$method: HTTP $status, try again later");
    }
    $reply = json_decode($text, true, 32, JSON_THROW_ON_ERROR);
    if ($reply['errorId'] !== 0) {
        // A refused create, or a task that failed or expired: errorCode says which. Neither is charged.
        throw new RuntimeException("$method: {$reply['errorCode']}: {$reply['errorDescription']}");
    }
    return $reply;
}

function solveTurnstile(array $task, int $deadline = 180): string
{
    $body = ['task' => $task];
    // Optional: where to POST the result when the task ends, instead of polling.
    // $body['callbackUrl'] = 'https://hooks.example.com/zerocaptcha';

    // One Idempotency-Key per task: sending the create again with it returns the same task.
    $created = zerocaptcha('createTask', $body, ['Idempotency-Key: ' . bin2hex(random_bytes(16))]);
    $stop = time() + $deadline;
    while (time() < $stop) {
        sleep(2);
        $result = zerocaptcha('getTaskResult', ['taskId' => $created['taskId']]);
        if ($result['status'] === 'ready') {
            return $result['solution']['token'];
        }
    }
    throw new RuntimeException("Task {$created['taskId']} is still running");
}

echo solveTurnstile([
    // 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' => ['action' => 'login', 'cdata' => 'session-7f3a9c2e'],
    // 'proxy' => 'http://user:pass@proxy.example.net:8080', // TurnstileTask only
]), PHP_EOL;

Good to know

Read next

PHP questions

Is there a PHP SDK?

No. The API is plain JSON over HTTPS, so the curl extension is enough, and a client written for createTask or 2Captcha's in.php can point at our host.

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.