Call the CAPTCHA API from Laravel

By solvethree3 min read

Use Laravel’s HTTP client to create a solvethree task, then retrieve that task by its ID. Keep the API key and proxy credentials on the server. A successful HTTP response isn’t enough: read the task status before using its solution.

On this page

Configure the site and server credentials

  1. 1.

    Activate the site profile

    Set up the target hostname in the console so your workspace has an active reCAPTCHA profile. Sending a hostname in a task doesn’t create one. See site profile setup.

  2. 2.

    Add the server configuration

    Add the example’s solvethree entry to the existing array in config/services.php. Set SOLVETHREE_API_KEY, CAPTCHA_SITE, and SOLVETHREE_PROXY in your server environment. Keep these values out of browser code; CAPTCHA_SITE is the hostname, not a full URL.

  3. 3.

    Match the target action

    The example sends login. Change it to the action used by your intended target request. It supplies one proxy; don’t also send proxyCandidates.

Create a service with separate create and retrieve methods

Laravel service · authenticated HTTP create and retrieve
<?php
// app/Services/CaptchaApi.php — configure services.solvethree in config/services.php.
namespace App\Services;

use Illuminate\Support\Facades\Http;
use InvalidArgumentException;

final class CaptchaApi
{
    private function client()
    {
        return Http::baseUrl('https://api.solvethree.com/v1')
            ->withToken(config('services.solvethree.key'))
            ->acceptJson()->asJson()->connectTimeout(10)->timeout(25);
    }

    public function create(string $requestId): array
    {
        if (!preg_match('/^[A-Za-z0-9_-]{1,64}$/', $requestId)) {
            throw new InvalidArgumentException('Invalid logical request ID');
        }
        // The caller keeps this requestId; do not generate another on a timeout.
        return $this->client()->post('/tasks?waitMs=0', [
            'site' => config('services.solvethree.site'),
            'action' => 'login',
            'proxy' => config('services.solvethree.proxy'),
            'requestId' => $requestId,
        ])->throw()->json();
    }

    public function retrieve(string $taskId): array
    {
        if (!preg_match('/^[A-Za-z0-9_-]{1,128}$/', $taskId)) {
            throw new InvalidArgumentException('Invalid task ID');
        }
        return $this->client()->get('/tasks/'.rawurlencode($taskId), ['waitMs' => 15000])
            ->throw()->json();
    }
}
// config/services.php entry:
// 'solvethree' => ['key' => env('SOLVETHREE_API_KEY'),
//   'site' => env('CAPTCHA_SITE'), 'proxy' => env('SOLVETHREE_PROXY')],
// Invoke from your authenticated controller/job. Poll retrieve only while pending
// or solving; ready contains the solution, failed is terminal. Never use retry()
// on create without preserving both this ID and the exact original inputs.

withToken sends the workspace key as Bearer authentication. The POST body is flat: site, action, proxy, and requestId are top-level fields. Don’t add a type or url field for reCAPTCHA. Omitting recaptchaType selects the documented enterprise-v3 default.

Both methods call throw() before json(). Laravel’s HTTP client doesn’t throw for unsuccessful HTTP responses by default, so this prevents the caller from treating an HTTP error payload as a successful task response. The service sets connection and request timeouts and deliberately has no automatic POST retry.

Keep the task ID and check its status

  1. 1.

    Create once for each logical solve

    From your authenticated controller or job, retain a caller-generated requestId before calling create. It must contain 1–64 ASCII letters, numbers, underscores, or hyphens. Save the returned task ID separately: the logical request ID is for creation recovery; the task ID is what retrieve accepts.

  2. 2.

    Retrieve the existing task

    Call retrieve with the saved task ID. If the status is pending or solving, arrange a later retrieval rather than running a tight loop in the controller. Give the caller an overall waiting deadline; the service’s HTTP timeout limits each request, not the lifetime of the solve. Reaching that deadline isn’t a reason to create another task.

  3. 3.

    Handle the terminal result

    For failed, stop retrieving and inspect errorCode and error; failed solves are refunded. For ready, use the returned solution token. Submit it with the returned userAgent and secChUa identity and the same proxy used for solving. A ready solution still has to be accepted by the target.

Keep HTTP errors separate from task failures. An unsuccessful HTTP response is handled by throw(); a successful response containing failed needs task-level handling. Don’t retry unchanged invalid inputs or assume every failure means insufficient credit. See API error handling.

Recover a timed-out creation without buying another solve

A connection timeout doesn’t tell you whether solvethree created the task. If you already have its task ID, retrieve it. Otherwise, repeat creation with the same requestId and exactly the same task inputs to recover the existing task.

Run your first task.

Start with $5 in workspace credit.

Create a workspace