Captcha solving service blog | SolveCaptcha How to solve and automatic bypass Google reCAPTCHA v2

How to bypass reCAPTCHA v2

reCAPTCHA v2 is one of the most common obstacles in browser automation, QA testing, and data collection.

The standard version displays an “I’m not a robot” checkbox. When Google requires additional verification, it may also show an image challenge asking the user to select objects such as buses, traffic lights, or crosswalks.

Invisible reCAPTCHA v2 works differently. It runs when the user clicks a button, submits a form, or performs another protected action. The checkbox may never appear, but the website still expects a valid reCAPTCHA response token.

For authorized automation, the most practical approach is to send the public captcha parameters to the SolveCaptcha API, retrieve the token, and submit it through the same field or callback used by the website.

The complete workflow is:

Open the protected page
→ identify reCAPTCHA v2
→ extract the site key
→ determine whether the widget is invisible
→ create a SolveCaptcha task
→ wait for the solution
→ retrieve the response token
→ update g-recaptcha-response
→ call the callback when required
→ continue the normal form workflow

This guide covers:

  • standard reCAPTCHA v2;
  • Invisible reCAPTCHA v2;
  • site key extraction;
  • SolveCaptcha task creation;
  • result polling;
  • SeleniumBase integration;
  • token insertion;
  • callback handling;
  • common errors.

Use this method only on websites you own or are authorized to test and automate.

How reCAPTCHA v2 works

A typical reCAPTCHA v2 integration has two parts.

The browser displays or executes the captcha widget. After verification, Google generates a short-lived token.

The website then submits that token to its backend, usually through a field named:

g-recaptcha-response

The backend sends the token to Google for verification. If the token is valid and matches the expected website configuration, the protected request can continue.

The normal process looks like this:

The page loads reCAPTCHA
→ the user completes verification
→ Google generates a token
→ the token is added to the form
→ the browser submits the form
→ the website verifies the token

SolveCaptcha replaces only the captcha-solving step. It does not replace the website’s session, cookies, CSRF protection, form validation, or other security controls.

Standard and Invisible reCAPTCHA v2

Both variants return a reCAPTCHA v2 token, but they are triggered differently.

Feature Standard reCAPTCHA v2 Invisible reCAPTCHA v2
Checkbox displayed Usually No
Trigger User clicks the widget Button click, form submit, or page action
Image challenge Possible Possible
Response field g-recaptcha-response g-recaptcha-response
Callback Optional Common
SolveCaptcha parameter No additional flag invisible=1

The same site key format is used for both versions.

The main difference in the SolveCaptcha request is:

invisible=1

How to identify standard reCAPTCHA v2

A standard widget often appears in the page HTML as:

<div
  class="g-recaptcha"
  data-sitekey="6Lc_aXkUAAAAAJs_eEHvoOl75_83eXSqpPSRFJ_u">
</div>

The public site key is the value of:

data-sitekey

You may also find it in a reCAPTCHA iframe URL:

https://www.google.com/recaptcha/api2/anchor?k=SITE_KEY

In that case, the site key is the value of the k parameter.

How to identify Invisible reCAPTCHA v2

Invisible reCAPTCHA commonly uses:

<button
  class="g-recaptcha"
  data-sitekey="SITE_KEY"
  data-size="invisible"
  data-callback="onCaptchaSolved">
  Continue
</button>

The main indicators are:

data-size="invisible"

and:

data-callback

The iframe URL may also contain:

size=invisible

Invisible reCAPTCHA may not be present when the page first loads. It can appear only after the user clicks the protected button.

How to find the site key in DevTools

Open the protected page and press:

F12

Then inspect the page using the Elements, Sources, or Network panel.

Search for:

data-sitekey
recaptcha/api2/anchor
grecaptcha.render
g-recaptcha

Copy the complete site key without adding spaces or quotation marks.

Browser-console parameter detector

The following script searches the most common reCAPTCHA v2 locations:

function detectRecaptchaV2() {
  const result = {
    siteKey: null,
    invisible: false,
    callbackName: null,
    domain: "google.com"
  };

  const widget = document.querySelector(
    ".g-recaptcha[data-sitekey],"
    + "[data-sitekey]"
  );

  if (widget) {
    result.siteKey =
      widget.getAttribute(
        "data-sitekey"
      );

    result.invisible =
      widget.getAttribute(
        "data-size"
      ) === "invisible";

    result.callbackName =
      widget.getAttribute(
        "data-callback"
      );
  }

  const frame = [
    ...document.querySelectorAll(
      'iframe[src*="/recaptcha/api2/anchor"]'
    )
  ][0];

  if (frame?.src) {
    try {
      const url = new URL(
        frame.src
      );

      result.siteKey =
        result.siteKey
        || url.searchParams.get("k");

      result.invisible =
        result.invisible
        || url.searchParams.get("size")
          === "invisible";

      result.domain =
        url.hostname.includes(
          "recaptcha.net"
        )
          ? "recaptcha.net"
          : "google.com";
    } catch {
      // Ignore malformed iframe URLs.
    }
  }

  return result;
}

console.log(
  detectRecaptchaV2()
);

Example output:

{
  "siteKey": "6Lc_aXkUAAAAAJs_eEHvoOl75_83eXSqpPSRFJ_u",
  "invisible": true,
  "callbackName": "onCaptchaSolved",
  "domain": "google.com"
}

Google test keys

Google provides public test keys for development environments.

A commonly used reCAPTCHA v2 test site key is:

6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI

Use test keys only in applications you control.

A test key does not bypass reCAPTCHA on an unrelated website. The site key and secret key must belong to the same reCAPTCHA configuration.

SolveCaptcha API endpoints

Create a captcha task through:

POST https://api.solvecaptcha.com/in.php

Retrieve the result through:

GET https://api.solvecaptcha.com/res.php

For reCAPTCHA v2, use:

method=userrecaptcha

The main parameters are:

Parameter Required Description
key Yes Your SolveCaptcha API key
method Yes Must be userrecaptcha
googlekey Yes Public reCAPTCHA site key
pageurl Yes Full URL containing the captcha
invisible For Invisible v2 Set to 1
domain No google.com or recaptcha.net
userAgent No User-Agent from the active browser
cookies No Cookies required by the current session
data-s No Additional value used by some Google services
json Recommended Set to 1 for JSON responses

Do not use unsupported request structures such as:

createTask
getTaskResult
clientKey
websiteKey
websiteURL
RecaptchaV2TaskProxyless
NoCaptchaTaskProxyless

The documented SolveCaptcha parameters are:

key
method
googlekey
pageurl
invisible

Create a standard reCAPTCHA v2 task

Example cURL request:

curl --request POST \
  --url https://api.solvecaptcha.com/in.php \
  --data-urlencode "key=YOUR_API_KEY" \
  --data-urlencode "method=userrecaptcha" \
  --data-urlencode "googlekey=6Lc_aXkUAAAAAJs_eEHvoOl75_83eXSqpPSRFJ_u" \
  --data-urlencode "pageurl=https://example.com/login" \
  --data-urlencode "json=1"

A successful response contains the task ID:

{
  "status": 1,
  "request": "2122988149"
}

Save the value returned in:

request

Create an Invisible reCAPTCHA v2 task

For Invisible reCAPTCHA, add:

invisible=1

Example:

curl --request POST \
  --url https://api.solvecaptcha.com/in.php \
  --data-urlencode "key=YOUR_API_KEY" \
  --data-urlencode "method=userrecaptcha" \
  --data-urlencode "googlekey=6Lc_aXkUAAAAAJs_eEHvoOl75_83eXSqpPSRFJ_u" \
  --data-urlencode "pageurl=https://example.com/login" \
  --data-urlencode "invisible=1" \
  --data-urlencode "json=1"

The task creation response uses the same format:

{
  "status": 1,
  "request": "2122988149"
}

Retrieve the token

Wait approximately:

15–20 seconds

before sending the first result request.

Then request the result:

curl "https://api.solvecaptcha.com/res.php?key=YOUR_API_KEY&action=get&id=2122988149&json=1"

If the task is still processing:

{
  "status": 0,
  "request": "CAPCHA_NOT_READY"
}

Wait five seconds and repeat the request using the same task ID.

Do not create another task when the result is not ready.

A completed task returns:

{
  "status": 1,
  "request": "03AFcWeA5dCJ..."
}

The value in request is the reCAPTCHA response token.

Install Python dependencies

Install Requests and SeleniumBase:

python -m pip install requests seleniumbase

Store the SolveCaptcha API key in an environment variable.

Linux or macOS:

export SOLVECAPTCHA_API_KEY="YOUR_API_KEY"

Windows PowerShell:

$env:SOLVECAPTCHA_API_KEY="YOUR_API_KEY"

Do not hardcode production API keys in source code.

Complete Python solver

Create solve_recaptcha_v2.py:

#!/usr/bin/env python3

from __future__ import annotations

import os
import time
from dataclasses import dataclass
from typing import Any

import requests

CREATE_TASK_URL = (
    "https://api.solvecaptcha.com/in.php"
)

GET_RESULT_URL = (
    "https://api.solvecaptcha.com/res.php"
)

INITIAL_WAIT_SECONDS = 20
POLL_INTERVAL_SECONDS = 5
MAX_WAIT_SECONDS = 180

class SolveCaptchaError(RuntimeError):
    """Raised when SolveCaptcha returns an error."""

@dataclass(frozen=True)
class RecaptchaSolution:
    task_id: str
    token: str

def parse_api_response(
    response: requests.Response,
) -> dict[str, Any]:
    response.raise_for_status()

    try:
        result = response.json()
    except ValueError as exc:
        raise SolveCaptchaError(
            "SolveCaptcha returned invalid JSON: "
            f"{response.text[:500]}"
        ) from exc

    if not isinstance(result, dict):
        raise SolveCaptchaError(
            f"Unexpected API response: {result!r}"
        )

    return result

def create_task(
    *,
    api_key: str,
    site_key: str,
    page_url: str,
    invisible: bool = False,
    domain: str = "google.com",
    user_agent: str | None = None,
    cookies: str | None = None,
    data_s: str | None = None,
) -> str:
    payload: dict[str, Any] = {
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "domain": domain,
        "json": 1,
    }

    if invisible:
        payload["invisible"] = 1

    if user_agent:
        payload["userAgent"] = (
            user_agent
        )

    if cookies:
        payload["cookies"] = cookies

    if data_s:
        payload["data-s"] = data_s

    response = requests.post(
        CREATE_TASK_URL,
        data=payload,
        timeout=30,
    )

    result = parse_api_response(
        response
    )

    if result.get("status") != 1:
        raise SolveCaptchaError(
            "Task creation failed: "
            f"{result.get('request')}"
        )

    task_id = str(
        result.get("request", "")
    ).strip()

    if not task_id:
        raise SolveCaptchaError(
            "SolveCaptcha did not return "
            "a task ID"
        )

    return task_id

def get_result(
    *,
    api_key: str,
    task_id: str,
) -> str | None:
    response = requests.get(
        GET_RESULT_URL,
        params={
            "key": api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        },
        timeout=30,
    )

    result = parse_api_response(
        response
    )

    if result.get("status") == 1:
        token = str(
            result.get("request", "")
        ).strip()

        if not token:
            raise SolveCaptchaError(
                "SolveCaptcha returned "
                "an empty token"
            )

        return token

    error_code = str(
        result.get(
            "request",
            "UNKNOWN_ERROR",
        )
    )

    if error_code == "CAPCHA_NOT_READY":
        return None

    raise SolveCaptchaError(
        f"Task failed: {error_code}"
    )

def solve_recaptcha_v2(
    *,
    api_key: str,
    site_key: str,
    page_url: str,
    invisible: bool = False,
    domain: str = "google.com",
    user_agent: str | None = None,
    cookies: str | None = None,
    data_s: str | None = None,
) -> RecaptchaSolution:
    task_id = create_task(
        api_key=api_key,
        site_key=site_key,
        page_url=page_url,
        invisible=invisible,
        domain=domain,
        user_agent=user_agent,
        cookies=cookies,
        data_s=data_s,
    )

    time.sleep(
        INITIAL_WAIT_SECONDS
    )

    deadline = (
        time.monotonic()
        + MAX_WAIT_SECONDS
    )

    while time.monotonic() < deadline:
        token = get_result(
            api_key=api_key,
            task_id=task_id,
        )

        if token is not None:
            return RecaptchaSolution(
                task_id=task_id,
                token=token,
            )

        time.sleep(
            POLL_INTERVAL_SECONDS
        )

    raise TimeoutError(
        f"Task {task_id} was not completed "
        f"within {MAX_WAIT_SECONDS} seconds"
    )

The solver can now be used from a browser automation script.

Extract reCAPTCHA parameters with SeleniumBase

Create the following helper:

from __future__ import annotations

from dataclasses import dataclass
from urllib.parse import (
    parse_qs,
    urlparse,
)

@dataclass(frozen=True)
class RecaptchaParameters:
    site_key: str
    invisible: bool
    callback_name: str | None
    domain: str

def detect_recaptcha(
    browser,
) -> RecaptchaParameters:
    result = browser.execute_script(
        """
        const result = {
          siteKey: null,
          invisible: false,
          callbackName: null,
          domain: "google.com"
        };

        const widget = document.querySelector(
          ".g-recaptcha[data-sitekey],"
          + "[data-sitekey]"
        );

        if (widget) {
          result.siteKey =
            widget.getAttribute(
              "data-sitekey"
            );

          result.invisible =
            widget.getAttribute(
              "data-size"
            ) === "invisible";

          result.callbackName =
            widget.getAttribute(
              "data-callback"
            );
        }

        const frame = [
          ...document.querySelectorAll(
            'iframe[src*="/recaptcha/api2/anchor"]'
          )
        ][0];

        if (frame?.src) {
          try {
            const url = new URL(
              frame.src
            );

            result.siteKey =
              result.siteKey
              || url.searchParams.get("k");

            result.invisible =
              result.invisible
              || url.searchParams.get("size")
                === "invisible";

            result.domain =
              url.hostname.includes(
                "recaptcha.net"
              )
                ? "recaptcha.net"
                : "google.com";
          } catch {
            // Ignore malformed iframe URLs.
          }
        }

        return result;
        """
    )

    site_key = str(
        result.get("siteKey") or ""
    ).strip()

    if not site_key:
        raise RuntimeError(
            "reCAPTCHA site key was not found"
        )

    callback_name = str(
        result.get("callbackName") or ""
    ).strip()

    return RecaptchaParameters(
        site_key=site_key,
        invisible=bool(
            result.get("invisible")
        ),
        callback_name=(
            callback_name or None
        ),
        domain=str(
            result.get("domain")
            or "google.com"
        ),
    )

Format browser cookies

Some integrations require cookies from the active browser session.

Use:

def format_cookies(
    browser,
) -> str | None:
    cookies = browser.driver.get_cookies()

    if not cookies:
        return None

    return "".join(
        f"{cookie['name']}:"
        f"{cookie['value']};"
        for cookie in cookies
        if cookie.get("name")
    )

Do not include authentication cookies unless they are genuinely required for an authorized workflow.

Apply the token to the page

The following helper updates every g-recaptcha-response field and calls the configured callback when one exists:

def apply_recaptcha_token(
    browser,
    *,
    token: str,
    callback_name: str | None,
) -> dict:
    return browser.execute_script(
        """
        const token = arguments[0];
        const callbackName = arguments[1];

        const fields = [
          ...document.querySelectorAll(
            '[name="g-recaptcha-response"]'
          )
        ];

        for (const field of fields) {
          field.value = token;
          field.innerHTML = token;

          field.dispatchEvent(
            new Event("input", {
              bubbles: true
            })
          );

          field.dispatchEvent(
            new Event("change", {
              bubbles: true
            })
          );
        }

        let callbackCalled = false;

        if (callbackName) {
          const callback = callbackName
            .split(".")
            .reduce(
              (object, key) =>
                object?.[key],
              window
            );

          if (
            typeof callback
            === "function"
          ) {
            callback(token);
            callbackCalled = true;
          }
        }

        return {
          fieldsUpdated: fields.length,
          callbackCalled
        };
        """,
        token,
        callback_name,
    )

Passing the token as a JavaScript argument is safer than inserting it directly into an f-string.

Complete SeleniumBase example

Create run_browser.py:

#!/usr/bin/env python3

from __future__ import annotations

import os
import time

from seleniumbase import SB

from solve_recaptcha_v2 import (
    solve_recaptcha_v2,
)

TARGET_URL = (
    "https://example.com/login"
)

def format_cookies(browser) -> str | None:
    cookies = browser.driver.get_cookies()

    if not cookies:
        return None

    return "".join(
        f"{cookie['name']}:"
        f"{cookie['value']};"
        for cookie in cookies
        if cookie.get("name")
    )

def detect_recaptcha(browser) -> dict:
    return browser.execute_script(
        """
        const result = {
          siteKey: null,
          invisible: false,
          callbackName: null,
          domain: "google.com"
        };

        const widget = document.querySelector(
          ".g-recaptcha[data-sitekey],"
          + "[data-sitekey]"
        );

        if (widget) {
          result.siteKey =
            widget.getAttribute(
              "data-sitekey"
            );

          result.invisible =
            widget.getAttribute(
              "data-size"
            ) === "invisible";

          result.callbackName =
            widget.getAttribute(
              "data-callback"
            );
        }

        const frame = [
          ...document.querySelectorAll(
            'iframe[src*="/recaptcha/api2/anchor"]'
          )
        ][0];

        if (frame?.src) {
          const url = new URL(
            frame.src
          );

          result.siteKey =
            result.siteKey
            || url.searchParams.get("k");

          result.invisible =
            result.invisible
            || url.searchParams.get("size")
              === "invisible";

          result.domain =
            url.hostname.includes(
              "recaptcha.net"
            )
              ? "recaptcha.net"
              : "google.com";
        }

        return result;
        """
    )

def apply_token(
    browser,
    *,
    token: str,
    callback_name: str | None,
) -> dict:
    return browser.execute_script(
        """
        const token = arguments[0];
        const callbackName = arguments[1];

        const fields = [
          ...document.querySelectorAll(
            '[name="g-recaptcha-response"]'
          )
        ];

        for (const field of fields) {
          field.value = token;
          field.innerHTML = token;

          field.dispatchEvent(
            new Event("input", {
              bubbles: true
            })
          );

          field.dispatchEvent(
            new Event("change", {
              bubbles: true
            })
          );
        }

        let callbackCalled = false;

        if (callbackName) {
          const callback = callbackName
            .split(".")
            .reduce(
              (object, key) =>
                object?.[key],
              window
            );

          if (
            typeof callback
            === "function"
          ) {
            callback(token);
            callbackCalled = true;
          }
        }

        return {
          fieldsUpdated: fields.length,
          callbackCalled
        };
        """,
        token,
        callback_name,
    )

def main() -> None:
    api_key = os.environ.get(
        "SOLVECAPTCHA_API_KEY",
        "",
    ).strip()

    if not api_key:
        raise SystemExit(
            "Set SOLVECAPTCHA_API_KEY"
        )

    with SB(
        headless=False
    ) as browser:
        browser.open(
            TARGET_URL
        )

        browser.sleep(3)

        parameters = detect_recaptcha(
            browser
        )

        site_key = str(
            parameters.get(
                "siteKey"
            )
            or ""
        ).strip()

        if not site_key:
            raise RuntimeError(
                "reCAPTCHA site key "
                "was not found"
            )

        user_agent = (
            browser.execute_script(
                "return navigator.userAgent"
            )
        )

        cookies = format_cookies(
            browser
        )

        solution = solve_recaptcha_v2(
            api_key=api_key,
            site_key=site_key,
            page_url=browser.get_current_url(),
            invisible=bool(
                parameters.get(
                    "invisible"
                )
            ),
            domain=str(
                parameters.get(
                    "domain"
                )
                or "google.com"
            ),
            user_agent=user_agent,
            cookies=cookies,
        )

        result = apply_token(
            browser,
            token=solution.token,
            callback_name=parameters.get(
                "callbackName"
            ),
        )

        print(
            "Task ID:",
            solution.task_id,
        )

        print(
            "Fields updated:",
            result["fieldsUpdated"],
        )

        print(
            "Callback called:",
            result["callbackCalled"],
        )

        # Continue the intended form workflow.
        # Use a specific selector for the correct form.
        browser.click(
            "#submit-button"
        )

        time.sleep(5)

if __name__ == "__main__":
    main()

Update:

TARGET_URL

and:

#submit-button

to match the application you are testing.

Do not make the response field visible

The g-recaptcha-response field is normally hidden.

There is no technical need to change its CSS:

field.style.display = "block";

Changing the appearance can affect page layout or application scripts.

Update its value directly and dispatch the appropriate browser events.

Handling Invisible reCAPTCHA callbacks

Invisible reCAPTCHA often defines a callback:

<div
  class="g-recaptcha"
  data-sitekey="SITE_KEY"
  data-size="invisible"
  data-callback="onSuccess">
</div>

After applying the token, call:

onSuccess(
  "TOKEN_FROM_SOLVECAPTCHA"
);

The callback may submit the form, enable a button, update application state, or send an API request.

Always update g-recaptcha-response before calling the callback unless the application’s documented integration requires a different sequence.

Nested callbacks

A callback may use a dotted path:

application.forms.onCaptchaSolved

Resolve it safely:

function resolveFunction(path) {
  const value = path
    .split(".")
    .reduce(
      (object, key) =>
        object?.[key],
      window
    );

  return typeof value === "function"
    ? value
    : null;
}

const callback = resolveFunction(
  "application.forms.onCaptchaSolved"
);

if (callback) {
  callback(
    "TOKEN_FROM_SOLVECAPTCHA"
  );
}

If the callback is defined inside a module, closure, or framework component, it may not be available through window.

In that case, inspect the website’s normal form submission or network request to determine where the token is expected.

Submit the correct form

After applying the token, continue the page’s normal workflow.

When submitting through JavaScript, prefer:

form.requestSubmit();

instead of:

form.submit();

The native submit() method skips normal submit events and browser validation.

Example:

const responseField =
  document.querySelector(
    '[name="g-recaptcha-response"]'
  );

const form =
  responseField?.closest("form");

if (!form) {
  throw new Error(
    "Captcha form was not found."
  );
}

form.requestSubmit();

Do not submit the first form on the page without confirming that it is connected to the captcha.

User-Agent and session consistency

Some integrations depend on the current browser session.

Keep these values consistent:

Page URL
User-Agent
Cookies
Browser session
Proxy

When passing userAgent to SolveCaptcha, use the exact value from:

navigator.userAgent

Do not replace it with a random modern User-Agent.

If the site requires session cookies, extract them from the same browser that will use the token.

Proxy considerations

A proxy is not always required for reCAPTCHA v2.

However, some websites associate the captcha with the source IP or use additional anti-bot checks.

When the application requires a consistent source address:

Load the page through Proxy A
→ create the captcha task with Proxy A
→ receive the token
→ submit the form through Proxy A

Changing the proxy during the workflow can invalidate the current page state or trigger another captcha.

Why a valid token may still fail

A SolveCaptcha token solves only the reCAPTCHA verification step.

The request may still fail because of:

  • an expired session;
  • an invalid CSRF token;
  • incorrect login credentials;
  • missing cookies;
  • a changed source IP;
  • a blocked account;
  • rate limiting;
  • another anti-bot system;
  • incorrect form data;
  • a server-side business rule.

Do not assume every rejected request means the captcha token was incorrect.

Inspect the complete server response.

Common API errors

ERROR_WRONG_USER_KEY

The SolveCaptcha API key is invalid.

Copy the key again from your account and remove any spaces or line breaks.

ERROR_ZERO_BALANCE

The account balance is insufficient.

Add funds before creating another task.

ERROR_CAPTCHA_UNSOLVABLE

The task could not be completed.

Check:

  • googlekey;
  • pageurl;
  • invisible;
  • loading domain;
  • optional cookies;
  • optional data-s;
  • whether the page generated a new challenge.

Capture fresh parameters before retrying.

CAPCHA_NOT_READY

The task is still processing.

Wait five seconds and request the result again using the same task ID.

Do not create a duplicate task.

Website-side troubleshooting

Token is rejected

Possible causes include:

  • incorrect site key;
  • incorrect page URL;
  • wrong captcha version;
  • token used after the widget reset;
  • missing callback;
  • token inserted into the wrong field;
  • changed browser session;
  • missing data-s;
  • another application validation error.

Compare your automated request with a successful manual request.

No g-recaptcha-response field exists

The field may be created only after the widget initializes.

Wait for:

[name="g-recaptcha-response"]

or trigger the protected action before applying the token.

A page can also submit the token through a custom request body rather than a visible form.

Callback was not called

Confirm the exact value of:

data-callback

The callback may not be globally accessible.

Captcha appears again

The website may have generated a new challenge because:

  • the token expired;
  • the page was reloaded;
  • the session changed;
  • the proxy changed;
  • another risk check failed.

Request a new token for the current page state.

Common implementation mistakes

Using createTask and getTaskResult

Incorrect:

createTask
getTaskResult
clientKey
taskId

Correct:

in.php
res.php
key
request

Using websiteKey instead of googlekey

Incorrect:

websiteKey=SITE_KEY

Correct:

googlekey=SITE_KEY

Using websiteURL instead of pageurl

Incorrect:

websiteURL=PAGE_URL

Correct:

pageurl=PAGE_URL

Using isInvisible instead of invisible

Incorrect:

isInvisible=true

Correct:

invisible=1

Updating only one response field

A page may contain multiple g-recaptcha-response fields.

Update every relevant field.

Calling only the callback

The form may still read g-recaptcha-response.

Update the field first.

Updating only the field

Invisible integrations may also require the configured callback.

Inserting the token with an f-string

Avoid:

browser.execute_script(
    f"onSuccess('{token}')"
)

Pass the token as a JavaScript argument instead.

Making the hidden field visible

Changing CSS is unnecessary and can interfere with the page.

Reusing a token

Treat every token as short-lived and single-use.

Reloading the page while waiting

Reloading can generate a new challenge and invalidate the pending solution.

Using the wrong form selector

A page may contain multiple forms.

Use a specific selector connected to the captcha field.

Best practices

Extract parameters from the current page

Do not store a site key, callback, or page URL indefinitely without checking that the integration is unchanged.

Detect Invisible mode automatically

Check:

data-size="invisible"

and:

size=invisible

Use environment variables

Keep API keys and proxy credentials outside source code.

Set a polling timeout

Do not wait indefinitely.

A practical configuration is:

Initial wait: 20 seconds
Polling interval: 5 seconds
Maximum wait: 180 seconds

Preserve the browser session

Avoid changing cookies, User-Agent, proxy, or page state before the token is submitted.

Log useful diagnostics

Record:

  • task ID;
  • page URL;
  • site key identifier;
  • Invisible mode;
  • callback name;
  • task creation time;
  • task completion time;
  • API errors;
  • number of updated fields.

Do not log:

  • API keys;
  • proxy passwords;
  • authentication cookies;
  • complete sensitive form data.

Pre-launch checklist

Before deploying the integration, confirm:

  • [ ] The target application is within the authorized scope.
  • [ ] The page uses reCAPTCHA v2.
  • [ ] The current site key was extracted.
  • [ ] The complete page URL is correct.
  • [ ] Invisible mode was detected correctly.
  • [ ] method=userrecaptcha is used.
  • [ ] The site key is sent as googlekey.
  • [ ] The page URL is sent as pageurl.
  • [ ] Invisible mode uses invisible=1.
  • [ ] The task ID is read from request.
  • [ ] The first result request waits 15–20 seconds.
  • [ ] Later result requests use five-second intervals.
  • [ ] The token is read from request.
  • [ ] Every relevant response field is updated.
  • [ ] Input and change events are dispatched.
  • [ ] The configured callback is called when required.
  • [ ] The correct form or button is used.
  • [ ] A fresh token is requested after the page resets.

Frequently asked questions

Which SolveCaptcha method should I use?

Use:

method=userrecaptcha

Where can I find the site key?

Look for:

data-sitekey

or the k parameter in the reCAPTCHA iframe URL.

Which API parameter receives the site key?

Use:

googlekey

How do I specify Invisible reCAPTCHA v2?

Add:

invisible=1

Where is the token returned?

With json=1, the completed token is returned in:

request

Where should the token be inserted?

The standard field is:

g-recaptcha-response

Some websites may use a callback or custom request field in addition to the standard response field.

Must I call the callback?

Call it when the page defines:

data-callback

Should I use SeleniumBase UC Mode?

Captcha solving and browser automation detection are separate concerns.

Use browser automation only within an authorized testing environment. Do not assume that changing the WebDriver configuration will bypass other security checks.

Is a proxy required?

Not always.

Use one when the authorized workflow requires a consistent source IP or location.

Can a token be reused?

No.

Treat it as short-lived and specific to the current challenge.

How often should I poll the API?

Wait approximately 15–20 seconds before the first request.

Continue every five seconds while the response is:

CAPCHA_NOT_READY

Why does the site still reject the request?

The website may require valid cookies, CSRF state, account data, headers, or other security checks in addition to reCAPTCHA.

Conclusion

Bypassing reCAPTCHA v2 with SolveCaptcha requires the correct site key, page URL, captcha mode, and token application flow.

The correct process is:

  1. Confirm that the page uses reCAPTCHA v2.
  2. Extract the site key from data-sitekey or the iframe URL.
  3. Determine whether the widget is standard or invisible.
  4. Record the callback name when one is configured.
  5. Submit method=userrecaptcha to in.php.
  6. Pass the site key as googlekey.
  7. Pass the full page URL as pageurl.
  8. Add invisible=1 for Invisible reCAPTCHA v2.
  9. Save the task ID returned in request.
  10. Wait approximately 15–20 seconds.
  11. Poll res.php every five seconds.
  12. Read the completed token from request.
  13. Update every relevant g-recaptcha-response field.
  14. Dispatch input and change events.
  15. Call the configured callback when required.
  16. Continue the page’s normal submission workflow.
  17. Preserve cookies, User-Agent, proxy, and page state.
  18. Request a fresh token when the captcha resets.

Use the SolveCaptcha reCAPTCHA v2 documentation to verify the current parameters before deploying the integration.