Solve reCAPTCHA Enterprise with PHP

By solvethree2 min read

You can call solvethree directly from PHP. The example below requests a reCAPTCHA v3 Enterprise token, follows the task until it finishes, and stops on a failed solve.

On this page

Before you run it

Use PHP 8+ with the cURL extension. Run it on your server, with the cURL extension enabled.

  • Create an active site profile for your target hostname in the console. See site profile setup.
  • Set SOLVETHREE_API_KEY to your workspace API key and SOLVETHREE_PROXY to your HTTP(S) proxy URL.
  • Replace accounts.example.com and login with your hostname and the action used by the page. Use a hostname for site, without a scheme or path.

Create the task and wait for the result

Save the code as solve.php and run php solve.php.

PHP 8 · solve.php
<?php
// Requires PHP 8+ with the cURL extension.
$apiKey = getenv('SOLVETHREE_API_KEY');
$proxy = getenv('SOLVETHREE_PROXY');
if (!$apiKey || !$proxy) throw new RuntimeException('Set the API key and proxy');

function api(string $path, ?array $body = null): array {
    global $apiKey, $deadline;
    $remaining = $deadline - microtime(true);
    if ($remaining <= 0) throw new RuntimeException("Keep the task ID and resume polling");
    $curl = curl_init('https://api.solvethree.com/v1' . $path);
    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT_MS => min(30000, max(1, (int) ceil($remaining * 1000))),
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'],
    ]);
    if ($body !== null) {
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($body, JSON_THROW_ON_ERROR));
    }
    $raw = curl_exec($curl);
    $error = curl_error($curl);
    $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    curl_close($curl);
    if ($raw === false) throw new RuntimeException('Connection failed: ' . $error);
    if ($status < 200 || $status >= 300) throw new RuntimeException('API HTTP ' . $status);
    return json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
}

$requestId = bin2hex(random_bytes(16)); // Save this with the task inputs for recovery.
$deadline = microtime(true) + 180;
$task = api('/tasks?waitMs=0', [
    'site' => 'accounts.example.com', 'action' => 'login',
    'proxy' => $proxy, 'requestId' => $requestId,
]);
while (in_array($task['status'], ['pending', 'solving'], true)) {
    if (microtime(true) >= $deadline) throw new RuntimeException('Keep the task ID and resume polling');
    $task = api('/tasks/' . rawurlencode($task['id']) . '?waitMs=15000');
    if (in_array($task['status'], ['pending', 'solving'], true)) usleep(1000000);
}
if ($task['status'] !== 'ready') throw new RuntimeException(
    ($task['errorCode'] ?? 'Unexpected task state') . ': ' . ($task['error'] ?? '')
);
$solution = $task['solution'];
// Use $solution['token'], ['userAgent'] and ['secChUa'] with the same proxy.
echo 'Ready task: ' . $task['id'] . PHP_EOL;

The POST uses waitMs=0 to return a task immediately. Each GET then waits up to 15 seconds for an update. The loop continues for pending or solving, and stops at ready or failed.

The request defaults to Enterprise v3. For standard v3, add recaptchaType: v3. Keep type and url out of a reCAPTCHA task body.

Use the token in your application

The token is $solution['token']. The task ID printed by the program is for retrieving the task; use the token value in your application's CAPTCHA field.

Use $solution['userAgent'] as User-Agent and $solution['secChUa'] as sec-ch-ua in the target request.

Send that request through the same proxy used for the solve. Then check the application response: a generated token does not establish that the target accepted the request.

If the request fails

A failed task includes errorCode and error; the example stops and reports them. Failed solves are refunded. An HTTP error such as 401 or 402 is a separate request problem—check the API error reference before retrying.

Keep the requestId and exact task inputs if you need recovery after a timeout or process restart. If you have the task ID, poll it. Otherwise, resend the original inputs with the same requestId. Simply restarting the program generates another ID and can create another billable solve.

The caller stops waiting after its deadline; that does not cancel a task already created. See polling and timeout recovery for the full sequence.

Run your first task.

Start with $5 in workspace credit.

Create a workspace