Captcha solving service blog | SolveCaptcha How to bypass captcha using Tampermonkey

How to bypass captcha using Tampermonkey and solver

Tampermonkey allows you to run custom JavaScript on selected websites. Combined with the SolveCaptcha API, it can detect a reCAPTCHA v2 widget, submit its parameters for solving, retrieve the response token, and insert the token into the current page.

This approach is useful for:

  • testing registration and login forms;
  • automating repetitive internal workflows;
  • debugging captcha integrations;
  • working with JavaScript-rendered pages;
  • authorized data collection;
  • reproducing user flows directly in a browser.

The example in this guide supports:

  • standard reCAPTCHA v2 checkbox;
  • Invisible reCAPTCHA v2;
  • dynamically loaded widgets;
  • data-callback functions;
  • optional automatic form submission;
  • correct and incorrect solution reporting.

Use the script only on websites you own or are authorized to test and automate.

What is Tampermonkey?

Tampermonkey is a userscript manager available for Chrome, Edge, Firefox, Safari, and other browsers.

A userscript is a JavaScript file that runs automatically on pages matching rules defined in its metadata.

A Tampermonkey script can:

  • inspect page elements;
  • monitor dynamically added content;
  • send API requests;
  • modify form fields;
  • execute page callbacks;
  • add custom controls;
  • store local configuration.

For captcha automation, Tampermonkey runs inside the same browser session where the captcha was generated. This helps preserve the current page URL, cookies, form state, and JavaScript context.

How the integration works

The complete workflow is:

Open protected page
→ detect reCAPTCHA v2
→ extract site key
→ send task to SolveCaptcha
→ receive task ID
→ wait for solution
→ retrieve response token
→ insert token into the page
→ execute callback
→ optionally submit the form

SolveCaptcha uses two API endpoints.

Create the task:

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

Retrieve the result:

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

For reCAPTCHA v2, the main request parameters are:

key
method=userrecaptcha
googlekey
pageurl

For Invisible reCAPTCHA v2, add:

invisible=1

Why the script uses GM_xmlhttpRequest

A normal page-level fetch() request can be blocked by browser CORS restrictions when it sends requests from the target website to the SolveCaptcha API.

Tampermonkey provides:

GM_xmlhttpRequest

This function performs the request through the extension and is not restricted by the target page’s ordinary CORS policy.

The userscript metadata must include:

// @grant   GM_xmlhttpRequest
// @connect api.solvecaptcha.com

Using GM_xmlhttpRequest is generally more reliable than adding header_acao=1 and depending on page-level fetch().

What you need

Before creating the script, prepare:

  • the Tampermonkey extension;
  • a SolveCaptcha account;
  • your SolveCaptcha API key;
  • sufficient account balance;
  • the domain where the script should run.

Your API key is available on the SolveCaptcha settings page.

Do not publish the key in a public userscript.

Install Tampermonkey

  1. Open tampermonkey.net.
  2. Install the extension for your browser.
  3. Open the Tampermonkey dashboard.
  4. Click Create a new script.
  5. Delete the default template.
  6. Paste the script from this guide.
  7. Change the @match rule to the required website.
  8. Save the script.

Configure the @match rule

Do not run a script containing your API key on every website unless this is absolutely necessary.

Avoid:

// @match *://*/*

Use a specific domain:

// @match https://example.com/*

For several authorized domains, add multiple rules:

// @match https://example.com/*
// @match https://accounts.example.net/*

This limits where the script can execute.

How to identify reCAPTCHA v2

Standard reCAPTCHA v2 commonly contains:

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

The public site key is stored in:

data-sitekey

It may also appear in a reCAPTCHA iframe URL:

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

Invisible reCAPTCHA may look like:

<button
  class="g-recaptcha"
  data-sitekey="6LeExampleSiteKey"
  data-size="invisible"
  data-callback="onCaptchaSolved">
  Submit
</button>

The script must extract:

googlekey = data-sitekey
pageurl = current page URL

Complete Tampermonkey script

Replace:

https://example.com/*

with the website where the script should run.

// ==UserScript==
// @name         SolveCaptcha reCAPTCHA v2 helper
// @namespace    https://solvecaptcha.com/
// @version      1.0.0
// @description  Solves reCAPTCHA v2 using the SolveCaptcha API
// @match        https://example.com/*
// @run-at       document-idle
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      api.solvecaptcha.com
// ==/UserScript==

(function () {
  "use strict";

  const CREATE_TASK_URL =
    "https://api.solvecaptcha.com/in.php";

  const GET_RESULT_URL =
    "https://api.solvecaptcha.com/res.php";

  const INITIAL_WAIT_MS = 20_000;
  const POLL_INTERVAL_MS = 5_000;
  const SOLVE_TIMEOUT_MS = 180_000;

  /*
   * Set to true only when the protected form
   * should be submitted automatically.
   */
  const AUTO_SUBMIT = false;

  let solving = false;
  let solvedWidgetKey = null;

  function sleep(milliseconds) {
    return new Promise((resolve) => {
      setTimeout(resolve, milliseconds);
    });
  }

  function setStatus(message, type = "info") {
    let box = document.getElementById(
      "solvecaptcha-userscript-status"
    );

    if (!box) {
      box = document.createElement("div");
      box.id =
        "solvecaptcha-userscript-status";

      Object.assign(box.style, {
        position: "fixed",
        right: "16px",
        bottom: "16px",
        zIndex: "2147483647",
        maxWidth: "340px",
        padding: "10px 14px",
        borderRadius: "6px",
        fontFamily:
          "Arial, sans-serif",
        fontSize: "13px",
        lineHeight: "1.4",
        boxShadow:
          "0 4px 18px rgba(0, 0, 0, 0.25)",
        color: "#ffffff",
        wordBreak: "break-word",
      });

      document.documentElement
        .appendChild(box);
    }

    const backgrounds = {
      info: "#1f2937",
      success: "#166534",
      error: "#991b1b",
      warning: "#92400e",
    };

    box.style.background =
      backgrounds[type]
      || backgrounds.info;

    box.textContent = message;
  }

  function getApiKey() {
    return String(
      GM_getValue(
        "solvecaptchaApiKey",
        ""
      )
    ).trim();
  }

  function setApiKey() {
    const currentKey = getApiKey();

    const newKey = window.prompt(
      "Enter your SolveCaptcha API key:",
      currentKey
    );

    if (newKey === null) {
      return;
    }

    const cleanKey = newKey.trim();

    if (!cleanKey) {
      GM_setValue(
        "solvecaptchaApiKey",
        ""
      );

      setStatus(
        "SolveCaptcha API key removed.",
        "warning"
      );

      return;
    }

    GM_setValue(
      "solvecaptchaApiKey",
      cleanKey
    );

    setStatus(
      "SolveCaptcha API key saved.",
      "success"
    );

    scanForCaptcha();
  }

  GM_registerMenuCommand(
    "Set SolveCaptcha API key",
    setApiKey
  );

  function gmRequest({
    method,
    url,
    data = null,
    headers = {},
  }) {
    return new Promise(
      (resolve, reject) => {
        GM_xmlhttpRequest({
          method,
          url,
          data,
          headers,
          timeout: 30_000,

          onload(response) {
            if (
              response.status < 200
              || response.status >= 300
            ) {
              reject(
                new Error(
                  `HTTP ${response.status}: `
                  + response.responseText
                )
              );

              return;
            }

            resolve(response);
          },

          ontimeout() {
            reject(
              new Error(
                "SolveCaptcha request timed out"
              )
            );
          },

          onerror(error) {
            reject(
              new Error(
                "SolveCaptcha network error: "
                + JSON.stringify(error)
              )
            );
          },
        });
      }
    );
  }

  function parseJsonResponse(response) {
    try {
      const result = JSON.parse(
        response.responseText
      );

      if (
        !result
        || typeof result !== "object"
      ) {
        throw new Error(
          "Unexpected response structure"
        );
      }

      return result;
    } catch (error) {
      throw new Error(
        "SolveCaptcha returned invalid JSON: "
        + response.responseText
          .slice(0, 500)
      );
    }
  }

  function findRecaptcha() {
    const candidates = [
      ...document.querySelectorAll(
        ".g-recaptcha[data-sitekey],"
        + "[data-sitekey]"
      ),
    ];

    for (const element of candidates) {
      const siteKey = element.getAttribute(
        "data-sitekey"
      );

      if (!siteKey) {
        continue;
      }

      const isInvisible =
        element.getAttribute(
          "data-size"
        ) === "invisible";

      const callbackName =
        element.getAttribute(
          "data-callback"
        );

      return {
        element,
        siteKey,
        isInvisible,
        callbackName,
      };
    }

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

    for (const frame of frames) {
      try {
        const url = new URL(frame.src);
        const siteKey =
          url.searchParams.get("k");

        if (!siteKey) {
          continue;
        }

        return {
          element: frame,
          siteKey,
          isInvisible:
            url.searchParams.get("size")
            === "invisible",
          callbackName: null,
        };
      } catch {
        // Ignore invalid iframe URLs.
      }
    }

    return null;
  }

  async function createTask(captcha) {
    const apiKey = getApiKey();

    if (!apiKey) {
      throw new Error(
        "SolveCaptcha API key is not configured"
      );
    }

    const parameters =
      new URLSearchParams({
        key: apiKey,
        method: "userrecaptcha",
        googlekey: captcha.siteKey,
        pageurl: window.location.href,
        json: "1",
      });

    if (captcha.isInvisible) {
      parameters.set(
        "invisible",
        "1"
      );
    }

    const response = await gmRequest({
      method: "POST",
      url: CREATE_TASK_URL,
      data: parameters.toString(),
      headers: {
        "Content-Type":
          "application/x-www-form-urlencoded",
      },
    });

    const result =
      parseJsonResponse(response);

    if (result.status !== 1) {
      throw new Error(
        "Unable to create captcha task: "
        + String(
          result.request
          || "UNKNOWN_ERROR"
        )
      );
    }

    const taskId = String(
      result.request
    ).trim();

    if (!taskId) {
      throw new Error(
        "SolveCaptcha did not return "
        + "a task ID"
      );
    }

    GM_setValue(
      "solvecaptchaLastTaskId",
      taskId
    );

    return taskId;
  }

  async function getTaskResult(taskId) {
    const apiKey = getApiKey();

    const url = new URL(
      GET_RESULT_URL
    );

    url.search =
      new URLSearchParams({
        key: apiKey,
        action: "get",
        id: taskId,
        json: "1",
      }).toString();

    const response = await gmRequest({
      method: "GET",
      url: url.toString(),
    });

    const result =
      parseJsonResponse(response);

    if (result.status === 1) {
      const token = String(
        result.request || ""
      ).trim();

      if (!token) {
        throw new Error(
          "SolveCaptcha returned "
          + "an empty token"
        );
      }

      return token;
    }

    const errorCode = String(
      result.request
      || "UNKNOWN_ERROR"
    );

    if (
      errorCode
      === "CAPCHA_NOT_READY"
    ) {
      return null;
    }

    throw new Error(
      "Captcha task failed: "
      + errorCode
    );
  }

  async function waitForToken(taskId) {
    setStatus(
      "Captcha task created. "
      + "Waiting for the solution..."
    );

    await sleep(INITIAL_WAIT_MS);

    const deadline =
      Date.now()
      + SOLVE_TIMEOUT_MS;

    while (Date.now() < deadline) {
      const token =
        await getTaskResult(taskId);

      if (token) {
        return token;
      }

      setStatus(
        "Captcha is still processing..."
      );

      await sleep(
        POLL_INTERVAL_MS
      );
    }

    throw new Error(
      "Captcha was not solved "
      + "within the configured timeout"
    );
  }

  function ensureResponseFields(captcha) {
    let fields = [
      ...document.querySelectorAll(
        '[name="g-recaptcha-response"]'
      ),
    ];

    if (fields.length > 0) {
      return fields;
    }

    const form =
      captcha.element.closest("form");

    const field =
      document.createElement(
        "textarea"
      );

    field.name =
      "g-recaptcha-response";

    field.id =
      "g-recaptcha-response";

    field.style.display = "none";

    if (form) {
      form.appendChild(field);
    } else {
      document.body.appendChild(
        field
      );
    }

    fields = [field];

    return fields;
  }

  function setToken(
    captcha,
    token
  ) {
    const fields =
      ensureResponseFields(captcha);

    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,
        })
      );
    }

    return fields.length;
  }

  function resolveCallback(
    callbackName
  ) {
    if (!callbackName) {
      return null;
    }

    const parts =
      callbackName.split(".");

    let value = window;

    for (const part of parts) {
      if (
        value === null
        || value === undefined
      ) {
        return null;
      }

      value = value[part];
    }

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

  function callCaptchaCallback(
    captcha,
    token
  ) {
    const callback =
      resolveCallback(
        captcha.callbackName
      );

    if (!callback) {
      return false;
    }

    callback(token);

    return true;
  }

  function submitClosestForm(
    captcha
  ) {
    const form =
      captcha.element.closest("form");

    if (!form) {
      return false;
    }

    if (
      typeof form.requestSubmit
      === "function"
    ) {
      form.requestSubmit();
    } else {
      form.submit();
    }

    return true;
  }

  async function solveCaptcha(
    captcha
  ) {
    if (solving) {
      return;
    }

    const widgetKey = [
      captcha.siteKey,
      window.location.href,
    ].join("|");

    if (
      solvedWidgetKey
      === widgetKey
    ) {
      return;
    }

    solving = true;

    try {
      setStatus(
        "Submitting reCAPTCHA v2 "
        + "to SolveCaptcha..."
      );

      const taskId =
        await createTask(captcha);

      setStatus(
        `Task created: ${taskId}`
      );

      const token =
        await waitForToken(taskId);

      const fieldsUpdated =
        setToken(
          captcha,
          token
        );

      const callbackCalled =
        callCaptchaCallback(
          captcha,
          token
        );

      solvedWidgetKey =
        widgetKey;

      setStatus(
        "Captcha solved. "
        + `Updated fields: ${fieldsUpdated}. `
        + `Callback called: ${callbackCalled}.`,
        "success"
      );

      if (AUTO_SUBMIT) {
        const submitted =
          submitClosestForm(
            captcha
          );

        if (!submitted) {
          setStatus(
            "Captcha solved, but no "
            + "parent form was found.",
            "warning"
          );
        }
      }
    } catch (error) {
      console.error(
        "[SolveCaptcha]",
        error
      );

      setStatus(
        error instanceof Error
          ? error.message
          : String(error),
        "error"
      );
    } finally {
      solving = false;
    }
  }

  function scanForCaptcha() {
    if (!getApiKey()) {
      setStatus(
        "Configure the SolveCaptcha "
        + "API key from the "
        + "Tampermonkey menu.",
        "warning"
      );

      return;
    }

    const captcha =
      findRecaptcha();

    if (!captcha) {
      return;
    }

    solveCaptcha(captcha);
  }

  async function reportLastTask(
    accepted
  ) {
    const apiKey = getApiKey();

    const taskId = String(
      GM_getValue(
        "solvecaptchaLastTaskId",
        ""
      )
    ).trim();

    if (!apiKey) {
      setStatus(
        "SolveCaptcha API key "
        + "is not configured.",
        "error"
      );

      return;
    }

    if (!taskId) {
      setStatus(
        "No previous task ID found.",
        "warning"
      );

      return;
    }

    if (
      !accepted
      && !window.confirm(
        "Report the last solution "
        + "as incorrect? Use this only "
        + "when the captcha answer itself "
        + "was rejected."
      )
    ) {
      return;
    }

    const action = accepted
      ? "reportgood"
      : "reportbad";

    const url = new URL(
      GET_RESULT_URL
    );

    url.search =
      new URLSearchParams({
        key: apiKey,
        action,
        id: taskId,
        json: "1",
      }).toString();

    try {
      const response =
        await gmRequest({
          method: "GET",
          url: url.toString(),
        });

      const result =
        parseJsonResponse(response);

      if (result.status !== 1) {
        throw new Error(
          String(
            result.request
            || "Unable to submit report"
          )
        );
      }

      setStatus(
        accepted
          ? "Correct solution reported."
          : "Incorrect solution reported.",
        "success"
      );
    } catch (error) {
      setStatus(
        error instanceof Error
          ? error.message
          : String(error),
        "error"
      );
    }
  }

  GM_registerMenuCommand(
    "Report last solution correct",
    () => reportLastTask(true)
  );

  GM_registerMenuCommand(
    "Report last solution incorrect",
    () => reportLastTask(false)
  );

  const observer =
    new MutationObserver(() => {
      scanForCaptcha();
    });

  observer.observe(
    document.documentElement,
    {
      childList: true,
      subtree: true,
    }
  );

  scanForCaptcha();
})();

Configure the API key

After saving the script:

  1. Open a page matched by the userscript.
  2. Click the Tampermonkey icon.
  3. Open the script commands.
  4. Select Set SolveCaptcha API key.
  5. Paste your API key.
  6. Confirm the prompt.
  7. Reload the page when necessary.

The key is stored through:

GM_setValue()

It is not inserted directly into the userscript source.

This is safer than writing:

const API_KEY =
  "YOUR_API_KEY";

However, the API key still exists in the browser extension’s local storage. Do not share the browser profile or export the script storage publicly.

How task creation works

The userscript sends:

key=YOUR_API_KEY
method=userrecaptcha
googlekey=SITE_KEY
pageurl=CURRENT_PAGE_URL
json=1

For Invisible reCAPTCHA v2, it also sends:

invisible=1

A successful response looks like:

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

The request value is the task ID.

How polling works

For reCAPTCHA, the script waits approximately:

20 seconds

before the first result request.

It then sends:

key=YOUR_API_KEY
action=get
id=TASK_ID
json=1

to:

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

If the solution is still being processed:

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

The script waits five seconds and repeats the request using the same task ID.

When the solution is ready:

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

The request value contains the reCAPTCHA token.

How the token is applied

The script finds all fields named:

g-recaptcha-response

and assigns the returned token:

field.value = token;
field.innerHTML = token;

It then dispatches:

input
change

events so that frameworks such as React, Vue, or Angular have a chance to detect the update.

If no response field exists, the script creates one inside the closest form.

Handling data-callback

Some reCAPTCHA implementations define a callback:

<div
  class="g-recaptcha"
  data-sitekey="SITE_KEY"
  data-callback="onCaptchaSolved">
</div>

The script detects:

data-callback

and calls:

onCaptchaSolved(token);

Nested callback names are also supported:

data-callback="app.forms.onCaptchaSolved"

The script resolves the function from window and passes the returned token.

Automatic form submission

The script contains:

const AUTO_SUBMIT = false;

This is disabled by default because automatically submitting a form can trigger:

  • account creation;
  • login attempts;
  • purchases;
  • password changes;
  • irreversible actions.

For an authorized workflow where automatic submission is required, change it to:

const AUTO_SUBMIT = true;

The script will then call:

form.requestSubmit();

on the closest parent form.

Some websites use JavaScript or XHR instead of an HTML form. In that case, automatic submission must be adapted to the website’s actual workflow.

Dynamic captcha widgets

Many websites do not load reCAPTCHA immediately. The widget may appear only after:

  • clicking a login button;
  • submitting a form;
  • opening a modal;
  • entering invalid credentials;
  • reaching a later checkout step;
  • triggering a risk rule.

The script uses:

MutationObserver

to monitor changes in the page DOM.

When a matching widget or iframe appears, the script detects it and creates a task.

Captcha inside an iframe

The visible reCAPTCHA challenge runs inside a cross-origin iframe. Tampermonkey normally cannot inspect the internal iframe DOM because of browser security restrictions.

Direct iframe access is not required for the token workflow.

The script obtains the site key from:

  • data-sitekey on the parent page;
  • the k parameter in the iframe URL.

Example iframe:

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

The returned token is inserted into the response field on the parent page.

Standard and Invisible reCAPTCHA v2

The script supports both common configurations.

Standard checkbox

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

SolveCaptcha request:

method=userrecaptcha
googlekey=SITE_KEY
pageurl=PAGE_URL

Invisible reCAPTCHA v2

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

SolveCaptcha request:

method=userrecaptcha
googlekey=SITE_KEY
pageurl=PAGE_URL
invisible=1

After receiving the token, the script calls the declared callback when available.

Reporting results

The userscript stores the most recent task ID.

Tampermonkey menu commands are added for:

Report last solution correct
Report last solution incorrect

A correct report sends:

action=reportgood

An incorrect report sends:

action=reportbad

Use reportgood when:

  • the website accepts the captcha token;
  • the protected action succeeds;
  • no captcha validation error appears.

Use reportbad only when the returned captcha token itself was rejected.

Do not report a solution as incorrect when the failure was caused by:

  • an expired page;
  • an incorrect form selector;
  • a missing callback;
  • another invalid form field;
  • a page reload;
  • a changed session;
  • application validation unrelated to captcha.

Common errors

ERROR_WRONG_USER_KEY

The API key is invalid.

Open the Tampermonkey menu and replace it with the key from the SolveCaptcha settings page.

ERROR_ZERO_BALANCE

The SolveCaptcha account has insufficient balance.

Add funds before creating another task.

ERROR_CAPTCHA_UNSOLVABLE

The captcha could not be solved reliably.

Check:

  • the site key;
  • the current page URL;
  • whether the widget was reset;
  • whether the page uses reCAPTCHA Enterprise;
  • whether the captcha is actually reCAPTCHA v2.

Create a new task only after verifying the parameters.

CAPCHA_NOT_READY

The task is still processing.

The script automatically waits five seconds and requests the result again.

Captcha not detected

Check whether the page contains:

data-sitekey

or:

recaptcha/api2/anchor

The widget may also be:

  • rendered inside another iframe;
  • loaded after user interaction;
  • placed inside a closed Shadow DOM;
  • generated by a custom wrapper;
  • implemented as reCAPTCHA Enterprise.

Open browser DevTools and inspect the page structure.

Token received but page does not continue

Possible causes include:

  • the website expects a callback not declared in data-callback;
  • the form must be submitted manually;
  • the token belongs in an XHR payload;
  • the widget reset before the token was applied;
  • the page uses a framework-specific state handler;
  • another form field is invalid.

Inspect the normal request in the browser’s Network tab and reproduce the same workflow.

CORS error

The provided script uses:

GM_xmlhttpRequest

and includes:

// @connect api.solvecaptcha.com

If requests still fail, verify that:

  • @grant GM_xmlhttpRequest is present;
  • @connect api.solvecaptcha.com is present;
  • the script was saved after editing metadata;
  • Tampermonkey has permission to run on the website.

Script runs repeatedly

The script tracks the site key and current page URL to avoid solving the same detected widget repeatedly.

If the website resets the widget without changing the URL, reload the page or adapt the reset detection to the website’s implementation.

Security recommendations

A Tampermonkey script has access to the pages specified in @match.

Follow these rules:

  • restrict @match to authorized domains;
  • never publish your API key;
  • do not use untrusted userscripts;
  • review every @connect domain;
  • disable automatic submission unless required;
  • do not share browser profiles containing stored keys;
  • monitor SolveCaptcha task statistics;
  • replace the key if it is exposed;
  • avoid running the script on payment or account-management pages without review.

What this script does not support

The example is designed for reCAPTCHA v2.

It should not be used unchanged for:

  • reCAPTCHA v3;
  • reCAPTCHA Enterprise;
  • Cloudflare Turnstile;
  • FunCaptcha;
  • GeeTest;
  • DataDome;
  • ALTCHA;
  • image captcha.

Each captcha type requires its own SolveCaptcha method and parameters.

For example:

reCAPTCHA v2:
method=userrecaptcha
Cloudflare Turnstile:
method=turnstile
ALTCHA:
method=altcha

Do not submit one captcha type through another method.

Tampermonkey or browser extension?

Tampermonkey is useful when you need:

  • website-specific logic;
  • custom selectors;
  • callback handling;
  • automatic form submission;
  • additional logging;
  • integration with an existing browser workflow.

The SolveCaptcha browser extension is more convenient when you need a ready-made interface without writing JavaScript.

Tampermonkey provides more control but requires maintenance when the target website changes.

Frequently asked questions

Can Tampermonkey solve captcha by itself?

No. Tampermonkey executes browser scripts. SolveCaptcha processes the captcha and returns the response token.

Which endpoint creates the task?

Use:

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

Which endpoint returns the result?

Use:

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

Which method is used for reCAPTCHA v2?

Use:

method=userrecaptcha

Which parameter contains the site key?

Use:

googlekey

Which parameter contains the page URL?

Use:

pageurl

How long should the script wait?

Wait approximately 15–20 seconds before the first reCAPTCHA result request. Repeat every five seconds while the API returns CAPCHA_NOT_READY.

Where is the token inserted?

The common response field is:

g-recaptcha-response

The website may also require a callback.

Why not use createTask and getTaskResult?

The documented SolveCaptcha workflow uses:

in.php
res.php

with parameters such as:

method
googlekey
pageurl

Should the API key be hardcoded?

No. Store it through Tampermonkey’s local storage functions and restrict the script to specific domains.

Can the script submit the form automatically?

Yes. Change:

const AUTO_SUBMIT = false;

to:

const AUTO_SUBMIT = true;

Review the target workflow before enabling it.

Can I use the same script on every website?

Technically possible, but not recommended. Restrict the @match rules to domains you are authorized to automate.

Conclusion

Tampermonkey and SolveCaptcha can automate reCAPTCHA v2 directly inside an active browser session.

The correct workflow:

  1. Install Tampermonkey.
  2. Create a userscript for the authorized target domain.
  3. Enable GM_xmlhttpRequest.
  4. Allow connections to api.solvecaptcha.com.
  5. Store the SolveCaptcha API key.
  6. Detect the reCAPTCHA site key.
  7. Submit method=userrecaptcha to in.php.
  8. Pass the site key as googlekey.
  9. Pass the complete page URL as pageurl.
  10. Wait approximately 20 seconds.
  11. Poll res.php every five seconds.
  12. Insert the returned token into g-recaptcha-response.
  13. Execute the configured callback when required.
  14. Submit the form only when the workflow is understood.
  15. Report accepted and genuinely incorrect solutions.

Use the SolveCaptcha API documentation to verify the current request parameters before deploying the script.