API Tutorials

How to Solve GeeTest v3 CAPTCHA with PHP

GeeTest v3 presents interactive challenges — slide puzzles, click sequences, or gap-fill tasks. Unlike token-based CAPTCHAs, GeeTest v3 returns three values (challenge, validate, seccode) that must be submitted together to the target site. CaptchaAI handles the visual solving and returns all three values.

This guide covers the full PHP implementation — extracting GeeTest parameters, submitting to CaptchaAI, and injecting the solution.


What you need

Requirement Details
CaptchaAI API key captchaai.com
PHP 7.4+ With curl extension
gt GeeTest public site key (static)
challenge Dynamic token loaded with the CAPTCHA (must be fresh)
Page URL Full URL where GeeTest appears
api_server (optional) GeeTest API server (e.g., api-na.geetest.com)

Step 1: Extract GeeTest parameters

Find the GeeTest parameters in the page source, typically in the initGeetest call:

// Look for this pattern in the page JavaScript:
initGeetest({
    gt: "f1ab2cdjja3456116012345b6c78d99e",
    challenge: "12345678abc90123d45678ef90123a456b",
    product: "bind",
    api_server: "api-na.geetest.com"
}, function(captchaObj) { ... });

Critical: The challenge value expires quickly. Always fetch a fresh challenge immediately before solving. Many sites expose a challenge endpoint (e.g., /api/geetest/register) that returns a fresh challenge and gt.


Step 2: Submit the task to CaptchaAI

<?php

$apiKey     = "YOUR_API_KEY";
$gt         = "f1ab2cdjja3456116012345b6c78d99e";
$challenge  = "12345678abc90123d45678ef90123a456b"; // must be fresh
$apiServer  = "api-na.geetest.com";
$pageUrl    = "https://example.com/login";

// Submit GeeTest task
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://ocr.captchaai.com/in.php?" . http_build_query([
    'key'        => $apiKey,
    'method'     => 'geetest',
    'gt'         => $gt,
    'challenge'  => $challenge,
    'api_server' => $apiServer,
    'pageurl'    => $pageUrl,
    'json'       => 1
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

if ($data['status'] !== 1) {
    die("Submit failed: " . $data['request']);
}

$taskId = $data['request'];
echo "Task submitted. ID: {$taskId}\n";

Step 3: Poll for the result

Wait 15 seconds, then poll every 5 seconds. The result contains three values:

// Poll for result
sleep(15);

$solution = null;

for ($i = 0; $i < 30; $i++) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://ocr.captchaai.com/res.php?" . http_build_query([
        'key'    => $apiKey,
        'action' => 'get',
        'id'     => $taskId,
        'json'   => 1
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $response = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($response, true);

    if ($result['status'] === 1) {
        $solution = json_decode($result['request'], true);
        echo "Challenge: " . $solution['challenge'] . "\n";
        echo "Validate:  " . $solution['validate'] . "\n";
        echo "Seccode:   " . $solution['seccode'] . "\n";
        break;
    }

    if ($result['request'] !== 'CAPCHA_NOT_READY') {
        die("Solve failed: " . $result['request']);
    }

    echo "Attempt " . ($i + 1) . ": not ready, waiting 5s...\n";
    sleep(5);
}

if ($solution === null) {
    die("Solve timed out");
}

Step 4: Submit the solution

Send all three values to the target site's verification endpoint:

// Submit GeeTest solution to target site
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com/api/login");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'username'           => 'user',
    'password'           => 'pass',
    'geetest_challenge'  => $solution['challenge'],
    'geetest_validate'   => $solution['validate'],
    'geetest_seccode'    => $solution['seccode']
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "Response status: {$httpCode}\n";

Complete working script

<?php

$apiKey     = "YOUR_API_KEY";
$gt         = "f1ab2cdjja3456116012345b6c78d99e";
$challenge  = "12345678abc90123d45678ef90123a456b";
$apiServer  = "api-na.geetest.com";
$pageUrl    = "https://example.com/login";

function solveGeetestV3($apiKey, $gt, $challenge, $apiServer, $pageUrl) {
    // Submit task
    $ch = curl_init("https://ocr.captchaai.com/in.php?" . http_build_query([
        'key'        => $apiKey,
        'method'     => 'geetest',
        'gt'         => $gt,
        'challenge'  => $challenge,
        'api_server' => $apiServer,
        'pageurl'    => $pageUrl,
        'json'       => 1
    ]));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $data = json_decode(curl_exec($ch), true);
    curl_close($ch);

    if ($data['status'] !== 1) {
        throw new Exception("Submit error: " . $data['request']);
    }

    $taskId = $data['request'];
    echo "Task ID: {$taskId}\n";

    // Poll for result
    sleep(15);
    for ($i = 0; $i < 30; $i++) {
        $ch = curl_init("https://ocr.captchaai.com/res.php?" . http_build_query([
            'key' => $apiKey, 'action' => 'get', 'id' => $taskId, 'json' => 1
        ]));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = json_decode(curl_exec($ch), true);
        curl_close($ch);

        if ($result['status'] === 1) {
            return json_decode($result['request'], true);
        }
        if ($result['request'] !== 'CAPCHA_NOT_READY') {
            throw new Exception("Solve error: " . $result['request']);
        }
        sleep(5);
    }

    throw new Exception("Solve timed out");
}

$solution = solveGeetestV3($apiKey, $gt, $challenge, $apiServer, $pageUrl);
echo "Challenge: " . $solution['challenge'] . "\n";
echo "Validate:  " . $solution['validate'] . "\n";
echo "Seccode:   " . $solution['seccode'] . "\n";

Expected output:

Task ID: 73849562810
Challenge: 12345678abc90123d45678ef90123a456b
Validate:  a1b2c3d4e5_validate
Seccode:   a1b2c3d4e5_seccode|jordan

Troubleshooting

Error Cause Fix
ERROR_BAD_PARAMETERS Missing gt or challenge Include both required parameters
ERROR_CAPTCHA_UNSOLVABLE Expired challenge token Get a fresh challenge immediately before submitting
ERROR_WRONG_USER_KEY Invalid API key Check key is 32 characters
ERROR_ZERO_BALANCE Insufficient balance Top up your account
Solution rejected by site Stale challenge The challenge must be used within seconds of generation
Solution rejected by site Wrong field names Check the site's expected parameter names (may vary)

FAQ

Why does my challenge keep expiring?

GeeTest challenge tokens have short lifespans. Fetch a fresh challenge from the site's registration endpoint immediately before submitting to CaptchaAI.

What are the three return values?

challenge is the original challenge token (may be modified), validate is the validation hash, and seccode is the security code. All three must be submitted together.

How do I find the api_server?

Look for api_server in the initGeetest configuration. Common values include api-na.geetest.com and api.geetest.com. If not specified, omit it from your request.

Does GeeTest v3 require a proxy?

Not required, but a proxy can improve success rates on sites that check IP consistency.

What is the typical solve time?

15–25 seconds for GeeTest v3 challenges.


Start solving GeeTest v3

Get your API key at captchaai.com. Use method=geetest with a fresh challenge token.


Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.