PHP में cURL cookie file के साथ AWS WAF कैप्चा कैसे हल करें

aws waf captcha in php - How to Solve AWS WAF CAPTCHA in PHP With a cURL Cookie File

PHP में AWS WAF कैप्चा हल करने के लिए पेज को cURL से fetch करें, HTTP 405 जवाब से पाँच values निकालें, और उन्हें CapSkip AWS WAF सॉल्वर के amazonWaf को दें, जो voucher जोड़ी लौटे उसे aws-waf-token से बदलें, और उस token को cookie file में लिखें। PHP process बाहर निकलते ही सब कुछ भूल जाता है, इसलिए token इसी file में रहता है। CapSkip आपके अपने Windows PC पर चलता है और प्रति-हल कोई शुल्क नहीं लेता।

आपको क्या चाहिए

  • Windows PC पर CapSkip 3.0.0 या बाद का, यानी वह वर्ज़न जिसमें AWS WAF सपोर्ट आया।
  • curl extension, Composer और capskip/capskip 1.4.0 या बाद के साथ PHP 8.0 या बाद का, जहाँ amazonWaf पहली बार आता है। नीचे सब कुछ PHP 8.0.30, 8.1.33, 8.2.29, 8.3.28, 8.4.15 और 8.5.0 (Windows के लिए WampServer builds) पर capskip 1.6.0 के साथ चला, और 8.0.30 तथा 8.5.0 पर 1.5.0 के साथ भी। वर्ज़न 1.6.0 ने hcaptcha जोड़ा और amazonWaf तथा HTTP layer को जैसा था वैसा रहने दिया।
  • एक CA bundle। छहों builds पर, curl.cainfo सेट न होने पर, किसी public साइट की https रिक्वेस्ट error 60, SSL certificate problem: unable to get local issuer certificate के साथ फेल हुई। शुरू करने से पहले php.ini में curl.cainfo में एक CA file का नाम दें।
  • एक PowerShell prompt, और ऐसा पेज जो आपका अपना हो या जिसे ऑटोमेट करने की आपको अनुमति हो, और जो CAPTCHA action वाले AWS WAF rule के पीछे हो।
  • CapSkip के लिए एक पता। Local mode उस PC पर 127.0.0.1 सर्व करता है जिस पर वह चल रहा है। Server mode API को आपके नेटवर्क या पब्लिक IP के लिए खोल देता है, ताकि दूसरी मशीन का PHP उसे कॉल कर सके। इनमें से एक यहाँ चुनें: कनेक्शन सेटिंग्स.

टेस्ट कैसे किया: मेरा लिखा एक mock, और कोई असली AWS WAF साइट नहीं। वह CapSkip की तरह भी जवाब देता है और संरक्षित पेज की तरह भी, और वे जाँचें लागू करता है जो CapSkip के API दस्तावेज़ के AWS WAF सेक्शन में सूचीबद्ध हैं। एक दूसरा listener TLS पर AWS के token host की तरह जवाब देता है, जिस तक https_proxy variable से पहुँचा जाता है, और curl.cainfo में एक throwaway certificate authority है ताकि certificate की जाँच चालू रहे। यहाँ कुछ भी हल का समय नहीं है, और mock जो भी नियम लागू करता है वह मेरा अपना है, AWS का दस्तावेज़ित व्यवहार नहीं।

चरण 1: SDK इंस्टॉल करें और 405 पेज पढ़ें

एक folder बनाएँ और SDK इंस्टॉल करें:

mkdir waf-demo
cd waf-demo
composer require capskip/capskip

इसके बाद waf.php बनाएँ। चरण 1 से 4 तक सब कुछ क्रम से उसी एक फ़ाइल में जाता है। पहले PAGE_URL को अपने पेज पर सेट करें, क्योंकि example.com पर कोई CAPTCHA rule नहीं है।

हर रिक्वेस्ट को नया cURL handle मिलता है, जो function के लौटते ही free हो जाता है। पेज fetch CURLOPT_COOKIEFILE सेट करते हैं, इसलिए jar में जो भी token हो, cURL उसे भेजता है, और वे जानबूझकर CURLOPT_COOKIEJAR सेट नहीं करते (FAQ देखें)। AWS के CAPTCHA action दस्तावेज़ के अनुसार puzzle पेज तब शामिल होता है जब रिक्वेस्ट में Accept header की value text/html हो, इसलिए helper ठीक वही भेजता है। cURL डिफ़ॉल्ट रूप से */* भेजता है, और AWS */* को text/html गिनता है या नहीं, यह मैं टेस्ट नहीं कर सका। मेरे mock ने खाली 405 भेजा।

<?php
// waf.php: Steps 1 to 4 go into this one file, in order.
require __DIR__ . '/vendor/autoload.php';

const PAGE_URL = 'https://www.example.com/'; // the page that answers HTTP 405
const JAR = __DIR__ . '/waf-cookies.txt';    // the token store, shared by every PHP process

// One cURL handle per request, freed when the function returns.
function request(string $url, array $options = []): array
{
    $ch = curl_init($url);
    curl_setopt_array($ch, $options + [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/130.0 Safari/537.36',
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        throw new RuntimeException(curl_error($ch));
    }
    return [curl_getinfo($ch, CURLINFO_RESPONSE_CODE), $body];
}

// A page fetch only reads the jar, so it sends whatever token is in the file.
function fetch(string $url): array
{
    return request($url, [
        CURLOPT_COOKIEFILE => JAR,
        CURLOPT_HTTPHEADER => ['Accept: text/html'], // cURL sends */* unless told otherwise
    ]);
}

मेल खाता CAPTCHA rule AWS WAF से header x-amzn-waf-action को captcha पर सेट करके 405 दिलवाता है। body window.gokuProps को key, iv और context entries वाले object पर सेट करती है, और awswaf.com host से challenge.js तथा captcha.js लोड करती है। SDK उदाहरण कहता है कि gokuProps.iv और gokuProps.context कुछ ही मिनटों में पुराने पड़ जाते हैं, इसलिए loop हर हल से पहले पेज दोबारा fetch करता है। function किसी भी दूसरे response के लिए null लौटाता है।

// The five values on an AWS WAF CAPTCHA page, or null for any other response.
function read_challenge(int $status, string $html): ?array
{
    if ($status !== 405 || !str_contains($html, 'gokuProps')) {
        return null;
    }
    preg_match('/gokuProps\s*=\s*\{(.*?)\}/s', $html, $block);
    $field = fn (string $name): ?string =>
        preg_match('/"' . $name . '"\s*:\s*"([^"]+)"/', $block[1] ?? '', $m) ? $m[1] : null;
    $script = fn (string $name): ?string =>
        preg_match('/<script[^>]+src="([^"]+\/' . $name . '\.js)"/', $html, $m) ? $m[1] : null;

    $found = [
        'sitekey' => $field('key'),
        'iv' => $field('iv'),
        'context' => $field('context'),
        'challenge_script' => $script('challenge'),
        'captcha_script' => $script('captcha'),
    ];
    return in_array(null, $found, true) ? null : $found;
}

चरण 2: amazonWaf को call करें

signature amazonWaf(string $sitekey, string $url, array $options = []) है। चैलेंज पेज पर sitekey, gokuProps.key होती है। gokuProps.iv और gokuProps.context की values साथ-साथ जाती हैं या बिल्कुल नहीं, और दोनों न हों तो हल शुरू होते समय CapSkip उन्हें पेज से पढ़ता है। SDK हर argument की जाँच तब करता है जब रिक्वेस्ट PHP से निकली भी नहीं होती, इसलिए गलत value तुरंत फेल होती है और mock उसे कभी देखता ही नहीं। errors table में वे मामले हैं जो मैंने आज़माए।

सफल call captchaId, captcha_voucher और existing_token वाला array लौटाती है, साथ में result की code entry के तहत कच्चा जवाब। यह token नहीं है: voucher जोड़ी को पहले बदलना पड़ता है। डिफ़ॉल्ट इंतज़ार 300 सेकंड है, क्योंकि CapSkip असली ब्राउज़र में हल करता है। SDK के सभी exceptions CapSkipError को extend करते हैं, और ApiException में CapSkip का error text होता है। proxy, proxy option के रूप में जाता है, यानी type HTTP और uri user:[email protected]:8080 वाला array, जो mock तक एक proxy और proxytype के रूप में पहुँचा। अपनी रिक्वेस्ट भी उसी proxy से भेजें, जिसे मैं टेस्ट नहीं कर सका।

use CapSkip\CapSkip;

// Local mode. In Server mode, use your server's address instead.
$solver = new CapSkip(['host' => '127.0.0.1', 'port' => 8080]);

function solve(CapSkip $solver, array $challenge): array
{
    // The answer is a voucher pair, not a token.
    return $solver->amazonWaf($challenge['sitekey'], PAGE_URL, [
        'iv' => $challenge['iv'],
        'context' => $challenge['context'],
        'challenge_script' => $challenge['challenge_script'],
        'captcha_script' => $challenge['captcha_script'],
    ]);
}

चरण 3: voucher को token से बदलें और सेव करें

वह folder लें जहाँ से challenge.js लोड हुई थी और उसमें voucher जोड़ें। वही endpoint है, और यह चरण आपकी अपनी रिक्वेस्ट है, इसलिए इस पर AWS आपका PHP पता देखता है। जोड़ी को JSON body के रूप में भेजें और जवाब का token field पढ़ें। CapSkip के दस्तावेज़ text/plain body के साथ एक Origin header इस्तेमाल करते हैं, और CapSkip app एक Referer जोड़ता है, इसलिए function तीनों भेजता है। string body के लिए cURL, जब तक कहा न जाए, form post घोषित करता है, जिसकी पुष्टि mock ने तब की जब मैंने header वाली लाइन हटाई। SDK का दस्तावेज़ जोड़ी को short-lived कहता है, इसलिए उसे तुरंत बदलें।

call redirects भी फॉलो करती है, क्योंकि CapSkip app के source में लिखा है कि id.region.sdk.awswaf.com रूप का regional token host hashed host पर redirect करता है। मेरे mock ने ऐसे host के लिए 307 भेजा और call ने उसे फॉलो किया। इस option के बिना वह HTTP 307 के साथ फेल हुई। मैंने कोई और code नहीं आज़माया।

// Trade the voucher pair for the aws-waf-token value.
function exchange(array $result, array $challenge): string
{
    // The voucher endpoint sits beside challenge.js, same folder.
    $script = $challenge['challenge_script'];
    $voucherUrl = substr($script, 0, strrpos($script, '/')) . '/voucher';
    $site = parse_url(PAGE_URL);
    $origin = $site['scheme'] . '://' . $site['host'] . (isset($site['port']) ? ':' . $site['port'] : '');

    [$status, $body] = request($voucherUrl, [
        CURLOPT_POST => true,
        CURLOPT_FOLLOWLOCATION => true, // a regional host may redirect to the real one
        CURLOPT_POSTFIELDS => json_encode([
            'captcha_voucher' => $result['captcha_voucher'],
            'existing_token' => $result['existing_token'],
        ]),
        CURLOPT_HTTPHEADER => [
            'Content-Type: text/plain;charset=UTF-8', // PHP would send a form type otherwise
            'Origin: ' . $origin,
            'Referer: ' . $origin . '/',
        ],
    ]);
    if ($status !== 200) {
        throw new RuntimeException("voucher exchange failed: HTTP $status");
    }
    return json_decode($body, true, 512, JSON_THROW_ON_ERROR)['token'];
}

jar एक Netscape-format text file है: हर लाइन में एक cookie, fields tabs से अलग। सेव करने वाला function ही इसे लिखने वाला इकलौता code है। वह बाकी हर लाइन रखता है, पुराना token बदलता है और exclusive lock के तहत लिखता है। लाइन host-only है, इसलिए cURL cookie को PAGE_URL के host को भेजता है, किसी और को नहीं। www और api नामों को एक ही मशीन की ओर पॉइंट करके किए गए probe में host-only लाइन www तक पहुँची पर api तक नहीं, जबकि शुरू में dot और subdomain field TRUE वाली लाइन दोनों तक पहुँची। Expiry 0 है क्योंकि मुझे साइट का immunity time पता नहीं।

कोई reader फ़ाइल को लिखे जाने के बीच पकड़ सकता है। एक process के 400 बार सेव करने और दूसरे के 400 रिक्वेस्ट भेजने पर, 8.4.15 पर 2 और 8.0.30 पर 1 रिक्वेस्ट बिना cookie के गईं। temp फ़ाइल लिखकर उसे rename करना Windows पर काम नहीं आया: 5 और 4 रिक्वेस्ट में फिर भी cookie नहीं थी, और rename खुद 400 में 1 और 7 बार फेल हुआ। अकेले आए 405 को retry की वजह मानें।

// The only writer of the jar. It keeps every other line and replaces the old token.
function save_token(string $token): void
{
    $lines = is_file(JAR) ? file(JAR, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
    $lines = array_filter($lines, fn (string $line) => !str_contains($line, "\taws-waf-token\t"));
    // Netscape format: host, subdomains, path, secure, expiry (0 = session), name, value
    $lines[] = parse_url(PAGE_URL, PHP_URL_HOST) . "\tFALSE\t/\tFALSE\t0\taws-waf-token\t" . $token;
    file_put_contents(JAR, implode("\n", $lines) . "\n", LOCK_EX);
}

चरण 4: सब कुछ lock के पीछे जोड़ें

आख़िरी ब्लॉक पूरा flow है। इसे चलाने के लिए php waf.php टाइप करें। इसकी पहली fetch jar से token भेजती है, इसलिए वैध token का मतलब है कि CapSkip से कभी संपर्क नहीं होता। सिर्फ़ 405 solve path में जाता है, और वह path lock से शुरू होता है, यानी वह हिस्सा जो सिर्फ़ PHP में मायने रखता है। scheduler से शुरू हुई दो scripts jar साझा करती हैं पर memory नहीं, इसलिए दोनों 405 देखेंगी और दोनों हल करेंगी। flock के साथ दूसरी इंतज़ार करती है, फिर से fetch करती है, पहली का token पाती है और लौट जाती है। function के खत्म होने पर handle के free होते ही PHP lock छोड़ देता है, और एक रन में अधिकतम तीन कोशिशें होती हैं।

use CapSkip\Exceptions\ApiException;

function fetch_protected(CapSkip $solver): array
{
    [$status, $html] = fetch(PAGE_URL); // sends the token in the jar, if there is one
    if (read_challenge($status, $html) === null) {
        return [$status, $html];
    }

    $lock = fopen(JAR . '.lock', 'c');
    flock($lock, LOCK_EX); // one solve at a time across processes, released when $lock is freed
    for ($attempt = 1; ; $attempt++) {
        [$status, $html] = fetch(PAGE_URL); // the process we waited for may have saved a token
        $challenge = read_challenge($status, $html);
        if ($challenge === null) {
            return [$status, $html];
        }
        if ($attempt > 3) {
            throw new RuntimeException('AWS WAF CAPTCHA not solved in 3 attempts');
        }
        try {
            save_token(exchange(solve($solver, $challenge), $challenge)); // exchange at once
        } catch (ApiException $e) {
            echo "attempt $attempt: {$e->getMessage()}\n"; // each retry draws a new puzzle
        }
    }
}

[$status, $body] = fetch_protected($solver);
echo $status, ' ', substr($body, 0, 60), "\n";

mock रन के नतीजे, छहों PHP वर्ज़न पर एक जैसे:

mock सेटिंगscript ने क्या किया
खाली jar, हर puzzle का जवाब दिया गयाठीक एक टास्क और एक exchange, 200 पर खत्म
दूसरा रन, वही jarबिना टास्क के 200, क्योंकि पहली रिक्वेस्ट पहले से cookie ले जा रही थी
पहले दो puzzle ठुकराए गएदो retry संदेश, तीसरे टास्क पर सफलता
पहले तीन puzzle ठुकराए गएतीन retry संदेश, एक uncaught RuntimeException, exit code 255, कोई voucher रिक्वेस्ट नहीं
साथ शुरू की गईं दो scripts, puzzle 2 सेकंड रोका गयाlock के साथ, एक टास्क और दोनों 200 पर खत्म। flock लाइन के बिना, दो या ज़्यादा टास्क

हर variant के लिए 10 बार दोहराने पर, 8.0.30 और 8.4.15 पर, lock ने सभी 20 रन में एक टास्क दिया। उसके बिना 20 में से 19 रन में दो टास्क बने और 8.4.15 पर एक रन में तीन। token तब तक चलता है जब तक साइट का immunity time खत्म न हो जाए, जिसे CapSkip के API दस्तावेज़ डिफ़ॉल्ट रूप से पाँच मिनट बताते हैं। इस window के भीतर का रन cookie भेजता है और CapSkip को छोड़ देता है, इसलिए scheduled job हर कुछ मिनट पर यह script बुला सकती है।

वेब रिक्वेस्ट के अंदर हल न करें

एक हल PHP वेब रिक्वेस्ट से लंबा चल सकता है। API दस्तावेज़ कहते हैं कि ज़्यादातर टास्क 15 सेकंड के भीतर तैयार हो जाते हैं और धीमी proxy इसमें जोड़ देती है, जबकि PHP मैनुअल वेब के लिए max_execution_time का डिफ़ॉल्ट 30 सेकंड देता है और command line के लिए कोई सीमा नहीं। मैंने पूरी script php-cgi.exe, एक web SAPI, के तहत 8.0.30 और 8.4.15 पर चलाई, ऐसे mock के सामने जो हर puzzle को 40 simulated सेकंड रोकता था। वह करीब 33 सेकंड बाद fatal error Maximum execution time of 30 seconds exceeded के साथ मर गई, क्योंकि PHP ने error तभी उठाया जब SDK के अंदर एक poll sleep खत्म हुई। php.exe के तहत वही script करीब 43 सेकंड चली।

छोटा SDK timeout भी इसे नहीं बचाता। 20 के timeout के साथ SDK करीब 0.25, 0.75, 1.75, 3.75, 7.75, 12.75 और 17.75 सेकंड पर poll करता है, फिर 22.75 तक सोता है और हार मान लेता है। 8.0.30 और 8.4.15 पर 16 या 17.5 सेकंड बाद तैयार हुआ puzzle करीब 18 सेकंड तक उठा लिया गया, लेकिन 19 सेकंड बाद तैयार हुआ छूट गया: call करीब 23 सेकंड पर TimeoutException के साथ खत्म हुई और जवाब कभी पढ़ा ही नहीं। डिफ़ॉल्ट 5 सेकंड की polling ceiling के साथ 20 का timeout सिर्फ़ उन जवाबों को पकड़ता है जो करीब 17.75 सेकंड तक तैयार हों।

वेब वाला हिस्सा read-only रखें और हल करने का काम schedule पर चलने वाले php waf.php के हवाले करें। यह ब्लॉक script की वेब कॉपी की आख़िरी दो लाइनों की जगह लेता है। 8.4.15 पर 40 सेकंड के mock puzzle के साथ php-cgi टेस्ट में, job के हल करते समय भेजी गई दो वेब रिक्वेस्ट को 0.25 सेकंड के भीतर Retry-After header के साथ 503 मिला। job करीब 43 सेकंड बाद एक टास्क और एक exchange के साथ पूरी हुई, और अगली वेब रिक्वेस्ट को असली पेज मिला।

// Web copy of waf.php: replace its last two lines with this read-only check.
// Solving stays with "php waf.php", run from a scheduled task.
[$status, $html] = fetch(PAGE_URL);
if (read_challenge($status, $html) !== null) {
    http_response_code(503);
    header('Retry-After: 60');
    exit('No valid token yet, try again soon.');
}
http_response_code($status);
echo substr($html, 0, 60);

आम errors

आप जो देखते हैंकारणफिक्स
ValidationExceptionSDK के भीतर, किसी भी रिक्वेस्ट से पहले उठा: अकेले भेजा गया gokuProps.iv या gokuProps.context, awswaf.com host पर न होने वाला https script पता, गलत नाम वाली script file, relative पेज URL या अज्ञात optionवही सुधारें जिसका संदेश नाम लेता है
संदेश में ERROR_CAPTCHA_UNSOLVABLE वाला ApiExceptionवह puzzle हल करने लायक नहीं था। हर नए टास्क को अलग puzzle मिलता है, इसका स्रोत: API दस्तावेज़कुछ नहीं: loop नया पेज fetch करता है और फिर submit करता है
NetworkExceptionhost और port पर कोई नहीं सुन रहा। बंद local port करीब 2 सेकंड बाद फेल हुआCapSkip शुरू करें और host और port दोबारा जाँचें
TimeoutExceptionइंतज़ार बिना जवाब के खत्म हुआ। यह ApiException के बजाय CapSkipError को extend करता है, इसलिए retry loop इसे निकल जाने देता हैपक्का करें कि CapSkip हल कर रहा है, या जहाँ हार माननी हो वहाँ इसे catch करें
ApiException को catch करने पर भी वह uncaught रहता हैCapSkip\CapSkip के लिए use line के बाद, CapSkip\Exceptions\ApiException लिखा catch ऐसी class का नाम लेता है जो मौजूद नहीं हैexception को import करें, या नाम की शुरुआत backslash से करें
RuntimeException: SSL certificate problem: unable to get local issuer certificatecurl.cainfo सेट नहीं है, इसलिए PHP पहली https रिक्वेस्ट पर certificate verify नहीं कर पाता, चाहे वह पेज fetch हो या voucher callphp.ini में curl.cainfo को किसी CA file की ओर पॉइंट करें
voucher exchange failed: HTTP 400CapSkip app का source voucher endpoint के 400 को null या अमान्य existing_token मानता है, जो संभवतः असली वजह है। मेरा mock उस जोड़ी पर 400 देता है जो उसने कभी जारी नहीं की, और मैं असली refusal दोहरा नहीं सकानए टास्क के साथ दौर दोहराएँ और तुरंत exchange करें
PHP 8.5 पर curl_close का नाम लेती Deprecated लाइनेंSDK के अंदर की Http.php फ़ाइल अब भी curl_close को call करती है, जिसे PHP 8.5.0 deprecate करता है। वह CapSkip को हर रिक्वेस्ट पर एक लाइन प्रिंट करती है, और हल फिर भी काम करता हैजब तक SDK यह call हटा न दे, E_DEPRECATED को error_reporting से बाहर रखें

FAQ

हर पेज fetch पर CURLOPT_COOKIEJAR क्यों न सेट करें?

handle अपना jar तब लिखता है जब PHP उसे free करता है, और जो handle token सेव होने से पहले फ़ाइल पढ़ चुका था, वह अपनी पुरानी सामग्री वापस लिख देता है। एक probe में ऐसा handle free होते ही token फ़ाइल से गायब हो गया, छहों वर्ज़न पर। अगर आपकी साइट ऐसी cookies सेट करती है जिन्हें रखना ज़रूरी है, तो उन्हें अलग jar दें।

widget वाला पेज इससे कैसे अलग है?

widget वाला पेज एक सामान्य form है जो jsapi.js लोड करता है और puzzle खुद बनाता है। SDK widget की apiKey को sitekey मानता है और jsapi.js का पता jsapiScript option के रूप में लेता है, iv या context के बिना। यह पोस्ट सिर्फ़ 405 पेज टेस्ट करती है, इसलिए चरण 1 के reader को widget के लिए दोबारा लिखना पड़ेगा।

क्या दूसरी मशीन का PHP Server mode में मेरा CapSkip इस्तेमाल कर सकता है?

हाँ। Server mode में CapSkip आपके नेटवर्क या पब्लिक IP पर सुनता है, इसलिए दूसरे PC, VPS या hosted platform पर चल रहा PHP constructor में अलग host और port देकर वही API call करता है, और बाकी कुछ नहीं बदलता। स्टैटिक पब्लिक IP से remote access स्थिर रहता है। हार्डवेयर आपका ही रहता है और उस पर कोई प्रति-हल शुल्क नहीं लगता। Server mode CapSkip का port हर पते के लिए खोल देता है, इसलिए उसे private रखने के लिए जिन मशीनों की आपको उम्मीद नहीं, उनके लिए Windows Firewall का block rule चाहिए। voucher exchange वहाँ चलता है जहाँ PHP चलता है, CapSkip वाली मशीन पर नहीं, और मैंने यह टेस्ट नहीं किया कि AWS को इससे फ़र्क़ पड़ता है या नहीं।

PHP में AWS WAF कैप्चा दो चीज़ों पर टिका है: token रखने वाली एक cookie file, और एक lock जो एक बार में एक ही process को हल करने देता है। चूँकि CapSkip आपकी अपनी मशीन पर चलने वाला एक कैप्चा सॉल्वर है, इसलिए हल न हो सकने वाला puzzle सिर्फ़ एक retry लेता है, पैसे नहीं। इस flow का Python संस्करण Python गाइड में है, और बाकी SDK की जानकारी यहाँ मिलती है: PHP कैप्चा सॉल्वर पेज.