DEV Community

Kaptra
Kaptra

Posted on Originally published at Medium

How to Solve reCAPTCHA v2 in Python for Automated Testing


A register form in your test suite gets as far as the button, then stops on the "I'm not a robot" checkbox. The page is fine. The script has no way to produce the token that checkbox is there to issue.

This walkthrough gets that token with Kaptra's captcha API and the ReCaptchaV2Task type: which values you copy off the page, what comes back, and why the token is useless if you wait too long to submit it.

The two values the page already has

ReCaptchaV2Task needs two strings:

  • websiteURL: the public URL of the form page.
  • websiteKey: the site key. On a normal checkbox widget, read the data-sitekey attribute on the reCAPTCHA element.

Leave the optional flags alone unless the page forces them. isInvisible and enterprise both default to false, and they should stay false unless you can see the invisible badge or the enterprise script on that page. proxy is also optional. You need it for a Cloudflare challenge page, because those cookies are tied to an IP. You do not need it here, because the result is a token rather than a cookie.

What you submit with the form

A ready task returns solution with:

  • gRecaptchaResponse, the token. Post it as the form field g-recaptcha-response.
  • userAgent. Use this user agent on the submit request.
  • expiresInMs. The docs sample is 120000, two minutes. Submit before that window closes. Solving the task and submitting the form are one step, not two sessions.

Full Python example

import time
import requests
# ---
BASE_URL = "https://api.kaptra.ai"
API_KEY = "YOUR_KAPTRA_API_KEY"
# ---
def solve_recaptcha_v2(website_url: str, website_key: str) -> dict:
    # 1. create the task; proxy is optional for this type
    resp = requests.post(
        f"{BASE_URL}/createTask",
        json={
            "clientKey": API_KEY,
            "task": {
                "type": "ReCaptchaV2Task",
                "websiteURL": website_url,
                "websiteKey": website_key,
            },
        },
    ).json()
    if resp.get("errorId"):
        raise RuntimeError(f"createTask failed: {resp.get('errorCode')}")
    task_id = resp["taskId"]
    # 2. poll every 1-2s, max 120 queries per task
    for _ in range(120):
        result = requests.post(
            f"{BASE_URL}/getTaskResult",
            json={"clientKey": API_KEY, "taskId": task_id},
        ).json()
        status = result.get("status")
        if status == "ready":
            return result["solution"]
        if status == "failure":
            raise RuntimeError(f"task failed: {result.get('errorCode')}")
        # processing (or CAPTCHA_NOT_READY): keep polling
        time.sleep(1.5)
    raise TimeoutError("gave up after 120 polls")
# ---
if __name__ == "__main__":
    solution = solve_recaptcha_v2(
        "https://www.example.com/register", "SITE_KEY"
    )
    print(solution["gRecaptchaResponse"])
    # 3. submit this token as g-recaptcha-response
    #    before expiresInMs, using solution["userAgent"]
Enter fullscreen mode Exit fullscreen mode

Poll every 1 to 2 seconds. The same taskId accepts at most 120 queries; past that you get ERROR_TOO_MANY_REQUESTS. CAPTCHA_NOT_READY means the worker is still on it, so keep polling. status ends at ready or failure.

Before you wire it into the suite

Do not flip flags to see if the solve gets better. isInvisible and enterprise describe the widget on the page. They are not quality settings. A visible standard checkbox wants both left at the default.

Failure is cheap. Average solve time is under 9 seconds. If the task fails you get ERROR_CAPTCHA_UNSOLVABLE; retry once with the same inputs. Failed and timed-out tasks are not billed. The price is $0.80 per 1,000 successful solves, with no monthly fee, and unused credit does not expire. A new account includes $10 in test credit, which is 12,500 solves at this rate.

Branch on errorId. It is 0 or 1. The detail is errorCode, which may also be ERROR_BAD_REQUEST or ERROR_TASK_NOT_SUPPORTED.

Where this sits next to the Cloudflare tasks

If the thing blocking your script is a widget inside a form, you want a site key and you will get a token back. That is this task, and it is also Turnstile, with a different task type and a different token field name. If the thing blocking your script is a full-screen "Verifying you are human" page, you want cookies and your own HTTP proxy, and this task will not help.

Top comments (0)