DEV Community

Cover image for reCAPTCHA Enterprise vs v3 vs v2: how to tell which one a site uses (enterprise.js, execute, invisible)
Bassem Shahin
Bassem Shahin

Posted on Edited on Originally published at dev.to

reCAPTCHA Enterprise vs v3 vs v2: how to tell which one a site uses (enterprise.js, execute, invisible)

Short answer: look at which script the page loads and how it asks for a token. api.js with a visible checkbox is reCAPTCHA v2. api.js with no checkbox, bound to a button or to grecaptcha.execute(), is v2 invisible. api.js?render=<sitekey> plus grecaptcha.execute(sitekey, {action}) is v3. enterprise.js, with calls under grecaptcha.enterprise.*, is reCAPTCHA Enterprise. Enterprise is not a fourth kind of challenge. It is Google Cloud's version of the same checkbox, invisible and score keys, and the site checks its tokens through a different backend API.

Getting this right matters because a token made for the wrong variant often comes back from the solver fine, submits fine, and is then rejected by the site's backend with no useful error. Below you'll find the signals side by side, a small Python detector, a Playwright check for what loads at runtime, and the solver parameters for each variant. I ran all of it against Google's own demo pages on 2026-09-29, and the outputs shown are from those runs.

reCAPTCHA v2 vs v3 vs Enterprise at a glance

Script What you see How the token is made What the site's backend gets
v2 checkbox recaptcha/api.js "I'm not a robot" box user ticks it (image grid if risky) success, hostname
v2 invisible recaptcha/api.js no box, only the badge a bound button or grecaptcha.execute() success, hostname
v3 recaptcha/api.js?render=<sitekey> the badge only grecaptcha.execute(sitekey, {action}) success, score, action
Enterprise recaptcha/enterprise.js any of the three above grecaptcha.enterprise.execute() or .render() tokenProperties, riskAnalysis (score and reasons)

Two things won't help you tell them apart. The sitekey won't: every variant uses a 40-character key that starts with 6L. The "protected by reCAPTCHA" badge won't either, because v2 invisible and v3 both show it. Some sites load the scripts from www.recaptcha.net instead of www.google.com, and the same rules apply.

reCAPTCHA Enterprise vs v3: what is actually different

This is the pair people mix up most. In the browser, an Enterprise score key behaves exactly like v3: there's no widget, just a badge and an execute() call with an action. The differences are these.

  • Where the key lives. Enterprise is a Google Cloud product. Its keys belong to a Cloud project, and they come in the same flavours as the classic ones: score, checkbox and invisible.
  • The client script. The page loads enterprise.js instead of api.js, and every call goes through grecaptcha.enterprise, for example grecaptcha.enterprise.execute(sitekey, {action: 'LOGIN'}). Its requests normally go to /recaptcha/enterprise/... rather than /recaptcha/api2/....
  • How the backend checks the token. Classic v2 and v3 post the token and a secret to https://www.google.com/recaptcha/api/siteverify. Enterprise creates an assessment through the Cloud API instead. The request looks roughly like this (authentication left out):
POST https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments
{"event": {"token": "TOKEN", "siteKey": "6Lc...", "expectedAction": "LOGIN",
           "userAgent": "Mozilla/5.0 ...", "userIpAddress": "203.0.113.7"}}
Enter fullscreen mode Exit fullscreen mode

The answer carries more than a score:

{"tokenProperties": {"valid": true, "action": "LOGIN", "hostname": "example.com"},
 "riskAnalysis": {"score": 0.3, "reasons": ["AUTOMATION"]}}
Enter fullscreen mode Exit fullscreen mode
  • More signals. reasons can include AUTOMATION, UNEXPECTED_ENVIRONMENT, TOO_MUCH_TRAFFIC or LOW_CONFIDENCE_SCORE. An invalid token also says why in tokenProperties.invalidReason, with values such as EXPIRED, DUPE or MALFORMED. Google documents 11 score levels for Enterprise, but by default a site sees only four: 0.1, 0.3, 0.7 and 0.9.

From the automation side, the browser mechanics are the same as v3 (or v2, for checkbox keys). What changes is how much the backend can compare. The site can pass Google the user agent and IP of the request that carried the token. A token produced in one browser and then submitted with a different user agent can therefore fail. That's why an Enterprise solve comes back with a user_agent you're expected to reuse. It helps to think of Enterprise as v3 with a backend that sees more, not as a harder version of v3.

v2 checkbox vs v2 invisible vs v3: the markup

v2 checkbox is a div with a sitekey, rendered by api.js:

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="6Lc..."></div>
Enter fullscreen mode Exit fullscreen mode

Some sites use explicit rendering instead: api.js?onload=onloadCallback&render=explicit plus grecaptcha.render(el, {sitekey: '6Lc...'}). That is still v2. Only render= followed by a sitekey means v3.

v2 invisible has three common markups. Google's own two demos use the two that never say "invisible":

<div class="g-recaptcha" data-sitekey="6Lc..." data-size="invisible" data-callback="onSubmit"></div>

<button class="g-recaptcha" data-sitekey="6Lc..." data-callback="onSubmit">Submit</button>

<div class="g-recaptcha" data-sitekey="6Lc..." data-bind="submit-btn" data-callback="onSuccess"></div>
Enter fullscreen mode Exit fullscreen mode

The programmatic version is grecaptcha.render(el, {sitekey: '6Lc...', size: 'invisible'}).

v3 has no element at all, just the keyed include and an execute() call:

<script src="https://www.google.com/recaptcha/api.js?render=6Lc..."></script>
<script>
  grecaptcha.ready(() =>
    grecaptcha.execute('6Lc...', {action: 'submit'}).then(sendTokenToBackend));
</script>
Enter fullscreen mode Exit fullscreen mode

At runtime, every key in use gets an anchor iframe. The iframe URL carries the sitekey as k= and a size=. A checkbox gets normal or compact. A v2 invisible widget gets invisible, and so does v3. So size=invisible alone doesn't separate v2 invisible from v3. The render=<sitekey> include and an execute() call with an action do.

Detect the reCAPTCHA version from the HTML (Python)

This script reads the page and its same-origin scripts. It reports each sitekey with its type, whether it's Enterprise, and the action or callback if it can find them. It's regex-based, so treat it as a triage tool rather than a parser.

# detect_recaptcha.py: which reCAPTCHA does a page use? Needs: pip install requests
import html
import re
import sys
from urllib.parse import parse_qs, urljoin, urlparse

import requests

KEY = r"6L[\w-]{38}"
SCRIPT = re.compile(r"""<script[^>]+src=["']([^"']*/recaptcha/(?:api|enterprise)\.js[^"']*)["']""", re.I)
TAG = re.compile(r"<(\w+)\s([^>]*data-sitekey[^>]*)>", re.I)
ATTR = re.compile(r"""([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')""")
RENDER = re.compile(r"grecaptcha(\.enterprise)?\.render\([^,]+,\s*\{([^}]*)\}")
EXECUTE = re.compile(rf"""grecaptcha(\.enterprise)?\.execute\(\s*['"]({KEY})['"]\s*,\s*\{{\s*action\s*:\s*['"]([^'"]+)['"]""")


def detect(url):
    ua = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/140.0 Safari/537.36"}
    page = requests.get(url, headers=ua, timeout=30).text
    code = page
    for src in re.findall(r"""<script[^>]+src=["']([^"']+\.js[^"']*)["']""", page)[:20]:
        src = urljoin(url, html.unescape(src))           # actions often live in bundles
        if urlparse(src).netloc == urlparse(url).netloc:
            code += requests.get(src, headers=ua, timeout=30).text

    keys, widget_script = {}, None
    for src in SCRIPT.findall(page):
        src = urljoin(url, html.unescape(src))
        enterprise = "/enterprise.js" in src
        render = parse_qs(urlparse(src).query).get("render", [""])[0]
        if re.fullmatch(KEY, render):                     # render=<sitekey>: score mode
            keys[render] = {"type": "v3", "enterprise": enterprise, "action": None}
        else:                                              # no key: this script renders widgets
            widget_script = "enterprise" if enterprise else "standard"

    for tag, attrs in TAG.findall(page):                   # implicit widgets
        a = {m[0].lower(): m[1] or m[2] for m in ATTR.findall(attrs)}
        invisible = a.get("data-size") == "invisible" or "data-bind" in a or tag.lower() == "button"
        keys[a["data-sitekey"]] = {"type": "v2-invisible" if invisible else "v2-checkbox",
                                   "enterprise": widget_script == "enterprise",
                                   "action": a.get("data-action"),
                                   "callback": a.get("data-callback")}
    for ent, body in RENDER.findall(code):                 # explicit grecaptcha.render(...)
        key = re.search(KEY, body)
        if key:
            invisible = re.search(r"""size['"]?\s*:\s*['"]invisible""", body)
            keys[key.group()] = {"type": "v2-invisible" if invisible else "v2-checkbox",
                                 "enterprise": bool(ent) or widget_script == "enterprise",
                                 "action": None}
    for ent, key, action in EXECUTE.findall(code):          # execute(key, {action})
        info = keys.setdefault(key, {"type": "v3", "enterprise": bool(ent)})
        info.update(type="v3", action=action, enterprise=info["enterprise"] or bool(ent))
    return [{"sitekey": key, **info} for key, info in keys.items()]


if __name__ == "__main__":
    for found in detect(sys.argv[1]):
        print(found)
Enter fullscreen mode Exit fullscreen mode

Here it is against four of Google's demo pages:

$ python detect_recaptcha.py https://recaptcha-demo.appspot.com/recaptcha-v2-invisible.php
{'sitekey': '6LcmDCcUAAAAAL5QmnMvDFnfPTP4iCUYRk2MwC0-', 'type': 'v2-invisible', 'enterprise': False, 'action': None, 'callback': 'onSubmit'}
$ python detect_recaptcha.py https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php
{'sitekey': '6LdKlZEpAAAAAAOQjzC2v_d36tWxCl6dWsozdSy9', 'type': 'v3', 'enterprise': False, 'action': 'examples/v3scores'}
$ python detect_recaptcha.py https://www.google.com/recaptcha/api2/demo
{'sitekey': '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-', 'type': 'v2-checkbox', 'enterprise': False, 'action': 'action', 'callback': 'onSuccess'}
$ python detect_recaptcha.py https://recaptcha-demo.appspot.com/recaptcha-v2-checkbox-explicit.php
{'sitekey': '6LfW6wATAAAAAHLqO2pb8bDBahxlMxNdo9g947u9', 'type': 'v2-checkbox', 'enterprise': False, 'action': None}
Enter fullscreen mode Exit fullscreen mode

On a page with enterprise.js?render=KEY and grecaptcha.enterprise.execute(KEY, {action: 'LOGIN'}) in a bundle, it reports 'type': 'v3', 'enterprise': True, 'action': 'LOGIN'.

It has limits. A minified bundle often passes the action as a variable ({action: e}), which a regex can't read. Scripts added by a tag manager aren't in the HTML at all. The runtime check below covers the second gap, and a DevTools breakpoint on the execute( line covers the first.

Confirm what loads at runtime (Playwright)

This one opens the page in a real browser and records every reCAPTCHA script and anchor iframe it requests.

# sniff_recaptcha.py: what does the page load at runtime?
# Needs: pip install playwright && playwright install chromium
import sys
from urllib.parse import parse_qs, urlparse

from playwright.sync_api import sync_playwright


def sniff(url):
    scripts, anchors = set(), []

    def on_request(req):
        u = urlparse(req.url)
        if "/recaptcha/" not in u.path:
            return
        name = u.path.rsplit("/", 1)[-1]
        if name in ("api.js", "enterprise.js"):
            scripts.add(req.url)
        elif name == "anchor":                            # one anchor iframe per key in use
            q = parse_qs(u.query)
            anchors.append({"sitekey": q.get("k", [""])[0],
                            "enterprise": "/enterprise/" in u.path,
                            "size": q.get("size", [""])[0],   # normal, compact or invisible
                            "sa": q.get("sa", [""])[0]})      # the widget's action, if set

    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.on("request", on_request)
        page.goto(url, wait_until="networkidle")
        namespace = page.evaluate("""() => ({
            grecaptcha: typeof window.grecaptcha !== 'undefined',
            enterprise: typeof window.grecaptcha?.enterprise !== 'undefined'})""")
        browser.close()
    return scripts, anchors, namespace


if __name__ == "__main__":
    scripts, anchors, namespace = sniff(sys.argv[1])
    for src in sorted(scripts):
        print("script:", src)
    for anchor in anchors:
        print("anchor:", anchor)
    print("namespace:", namespace)
Enter fullscreen mode Exit fullscreen mode

Here's the output on Google's v3 demo:

$ python sniff_recaptcha.py https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php
script: https://www.google.com/recaptcha/api.js?render=6LdKlZEpAAAAAAOQjzC2v_d36tWxCl6dWsozdSy9
script: https://www.google.com/recaptcha/enterprise.js?render=6Le80pApAAAAANg24CMbhL_U2PASCW_JUnq5jPys
anchor: {'sitekey': '6LdKlZEpAAAAAAOQjzC2v_d36tWxCl6dWsozdSy9', 'enterprise': False, 'size': 'invisible', 'sa': ''}
anchor: {'sitekey': '6Le80pApAAAAANg24CMbhL_U2PASCW_JUnq5jPys', 'enterprise': True, 'size': 'invisible', 'sa': ''}
namespace: {'grecaptcha': True, 'enterprise': True}
Enter fullscreen mode Exit fullscreen mode

This output shows the trap. Every page on recaptcha-demo.appspot.com loads a second key through enterprise.js at runtime, and that key isn't in the HTML. The v3 form itself uses 6LdKlZEp... through api.js. If you had gone by "the page loads enterprise.js", you would have solved the wrong key in the wrong mode.

The anchor path doesn't settle it either. The checkbox and invisible demos load both scripts too, and in most of my runs on them the form's own classic widget was served from /recaptcha/enterprise/anchor, on the invisible demo as well. Yet the demo's backend accepted a standard solve for the invisible demo's key, with no enterprise=1 (it's the Python run further down).

So here's the rule that holds up. Start from the sitekey the form actually uses. Then follow that key: which script include carries it, and whether grecaptcha or grecaptcha.enterprise calls execute() or render() with it.

Solver parameters for each reCAPTCHA variant

These are the parameter names of the 2Captcha-style in.php/res.php API, which several solving services share. If you're moving an existing client from one provider to another, the switch is mostly a base-URL change.

Variant Add to method=userrecaptcha, googlekey, pageurl Token in the res.php answer
v2 checkbox nothing request
v2 invisible invisible=1 (required) request
v2 Enterprise enterprise=1, plus action= set to the anchor's sa= value if it has one result, plus user_agent
v3 version=v3 and action= set to the execute() action (both required) request
v3 Enterprise version=v3, action=... and enterprise=1 result, plus user_agent

There's no parameter for the score you want, and no min_score. The site's backend sets the threshold. What you control is the action, the timing and the user agent.

One client covers all five. It takes an entry from detect_recaptcha.py:

# solve_recaptcha.py: one client for all five variants. Needs: pip install requests
import os
import re
import time

import requests

API = "https://ocr.captchaai.com"                      # any 2Captcha-style in.php/res.php API
API_KEY = os.environ.get("CAPTCHA_API_KEY", "YOUR_API_KEY")
RETRYABLE = {"ERROR_SERVER_ERROR", "ERROR_INTERNAL_SERVER_ERROR"}


def build_params(info, pageurl):
    """info is one entry from detect_recaptcha.py: type, enterprise, action."""
    p = {"key": API_KEY, "method": "userrecaptcha", "googlekey": info["sitekey"],
         "pageurl": pageurl, "json": 1}
    if info["type"] == "v2-invisible":
        p["invisible"] = 1                               # required for invisible widgets
    if info["type"] == "v3":
        if not info.get("action"):
            raise ValueError("v3 needs the action passed to execute(); find it first")
        p.update(version="v3", action=info["action"])
    if info["enterprise"]:
        p["enterprise"] = 1
        if info["type"] != "v3" and info.get("action"):
            p["action"] = info["action"]                  # v2 Enterprise: the sa= value
    return p


def solve(params, timeout=180):
    if params["key"] in ("", "YOUR_API_KEY"):
        raise SystemExit("set CAPTCHA_API_KEY first")
    for attempt in range(3):                              # submit, retrying server errors
        r = requests.post(f"{API}/in.php", data=params, timeout=30).json()
        if r["status"] == 1:
            break
        if r["request"] not in RETRYABLE:                 # bad key, bad sitekey, no balance
            raise RuntimeError(f"submit failed: {r['request']}")
        time.sleep(10 * (attempt + 1))
    else:
        raise RuntimeError("submit failed after 3 tries")

    task_id, deadline = r["request"], time.time() + timeout
    time.sleep(15)                                        # nothing is ready before ~15 s
    while time.time() < deadline:
        try:
            r = requests.get(f"{API}/res.php", timeout=30, params={
                "key": params["key"], "action": "get", "id": task_id, "json": 1}).json()
        except requests.RequestException as exc:          # network blip: keep polling
            print("poll error:", exc)
        else:
            if r["status"] == 1:
                # standard answers put the token in "request"; Enterprise answers use
                # "result" and add the solver's "user_agent", which you must reuse
                return r.get("result") or r["request"], r.get("user_agent")
            if r["request"] != "CAPCHA_NOT_READY":        # ERROR_CAPTCHA_UNSOLVABLE, ...
                raise RuntimeError(f"solve failed: {r['request']}")
        time.sleep(5)
    raise TimeoutError(f"task {task_id} not solved in {timeout}s")


if __name__ == "__main__":
    # Google's public invisible v2 demo, as detect_recaptcha.py reported it
    PAGE = "https://recaptcha-demo.appspot.com/recaptcha-v2-invisible.php"
    info = {"sitekey": "6LcmDCcUAAAAAL5QmnMvDFnfPTP4iCUYRk2MwC0-",
            "type": "v2-invisible", "enterprise": False, "action": None}
    token, user_agent = solve(build_params(info, PAGE))
    print("token:", token[:20] + "...", "| user_agent:", user_agent)

    # submit it the way the page's form does: POST with g-recaptcha-response
    headers = {"User-Agent": user_agent} if user_agent else {}
    reply = requests.post(PAGE, headers=headers, timeout=30,
                          data={"ex-a": "foo", "ex-b": "bar", "g-recaptcha-response": token})
    print("demo backend:", re.findall(r"'(success|hostname)' => '?([\w.-]+)", reply.text))
Enter fullscreen mode Exit fullscreen mode
$ CAPTCHA_API_KEY=... python solve_recaptcha.py
token: 0cAFcWeA4d2qPybEZsxn... | user_agent: None
demo backend: [('success', 'true'), ('hostname', 'recaptcha-demo.appspot.com')]
Enter fullscreen mode Exit fullscreen mode

The same client in Node 18+ (built-in fetch, no packages), pointed at the v3 demo this time:

// solve_recaptcha.mjs: the same client for Node 18+ (built-in fetch, no packages)
const API = "https://ocr.captchaai.com";
const API_KEY = process.env.CAPTCHA_API_KEY ?? "YOUR_API_KEY";
const RETRYABLE = new Set(["ERROR_SERVER_ERROR", "ERROR_INTERNAL_SERVER_ERROR"]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const getJson = async (url, init = {}) =>
  (await fetch(url, { ...init, signal: AbortSignal.timeout(30_000) })).json();

function buildParams(info, pageurl) {
  const p = { key: API_KEY, method: "userrecaptcha", googlekey: info.sitekey, pageurl, json: "1" };
  if (info.type === "v2-invisible") p.invisible = "1";
  if (info.type === "v3") {
    if (!info.action) throw new Error("v3 needs the action passed to execute(); find it first");
    Object.assign(p, { version: "v3", action: info.action });
  }
  if (info.enterprise) {
    p.enterprise = "1";
    if (info.type !== "v3" && info.action) p.action = info.action; // v2 Enterprise: sa= value
  }
  return p;
}

async function solve(params, timeoutMs = 180_000) {
  if (!params.key || params.key === "YOUR_API_KEY") throw new Error("set CAPTCHA_API_KEY first");
  let r;
  for (let attempt = 0; ; attempt++) {
    r = await getJson(`${API}/in.php`, { method: "POST", body: new URLSearchParams(params) });
    if (r.status === 1) break;
    if (!RETRYABLE.has(r.request) || attempt === 2) throw new Error(`submit failed: ${r.request}`);
    await sleep(10_000 * (attempt + 1));
  }
  const id = r.request;
  const deadline = Date.now() + timeoutMs;
  await sleep(15_000);
  while (Date.now() < deadline) {
    const q = new URLSearchParams({ key: params.key, action: "get", id, json: "1" });
    try {
      r = await getJson(`${API}/res.php?${q}`);
    } catch (err) {
      console.log("poll error:", err.message); // network blip: keep polling
      await sleep(5_000);
      continue;
    }
    // Enterprise answers carry the token in "result" plus the solver's "user_agent"
    if (r.status === 1) return { token: r.result ?? r.request, userAgent: r.user_agent ?? null };
    if (r.request !== "CAPCHA_NOT_READY") throw new Error(`solve failed: ${r.request}`);
    await sleep(5_000);
  }
  throw new Error(`task ${id} not solved in ${timeoutMs / 1000}s`);
}

// Google's public v3 demo, as detect_recaptcha.py reported it
const PAGE = "https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php";
const info = { sitekey: "6LdKlZEpAAAAAAOQjzC2v_d36tWxCl6dWsozdSy9", type: "v3",
               enterprise: false, action: "examples/v3scores" };
const { token, userAgent } = await solve(buildParams(info, PAGE));
console.log("token:", token.slice(0, 20) + "...", "| userAgent:", userAgent);

// the same check the demo page's own JavaScript runs with its token
const q = new URLSearchParams({ action: info.action, token });
console.log(await getJson(`https://recaptcha-demo.appspot.com/recaptcha-v3-verify.php?${q}`));
Enter fullscreen mode Exit fullscreen mode
$ CAPTCHA_API_KEY=... node solve_recaptcha.mjs
token: 0cAFcWeA6F-w3KX697Jj... | userAgent: null
{
  success: true,
  hostname: 'recaptcha-demo.appspot.com',
  challenge_ts: '2026-09-29T07:53:00Z',
  apk_package_name: null,
  score: 0.9,
  action: 'examples/v3scores',
  'error-codes': []
}
Enter fullscreen mode Exit fullscreen mode

That's one run on a demo page, so don't read the 0.9 as a promise for your own target.

Where the token goes depends on the variant:

  • v2: it goes into the g-recaptcha-response textarea. If the widget has a data-callback, call that function with the token as well, because on callback-driven forms filling the textarea alone does nothing.
  • v3 and Enterprise: it goes into whatever field the page's own request uses. Watch the Network tab. It might be g-recaptcha-response in a form post, or a JSON field in an XHR.
  • Enterprise: also send the request with the user_agent from the answer as your User-Agent header.

Why a correctly solved token still gets rejected

  • Expired or reused. A token is valid for two minutes and can be verified only once. Classic siteverify reports this as timeout-or-duplicate, and Enterprise reports EXPIRED or DUPE. Solve once per request and submit right away.
  • Action mismatch. The backend compares the token's action with the one it expects. Google's PHP library, which the demos use, reports this as action-mismatch. Copy the string exactly, including case and slashes.
  • Wrong variant. This covers v3 parameters sent for a v2 invisible key, a missing invisible=1, and a missing enterprise=1. When the API can tell that the key belongs to another type, in.php answers ERROR_WRONG_KEY_TYPE. Often nothing tells you.
  • User agent mismatch on Enterprise. Reuse the user_agent that came back with the token.
  • Wrong page URL. Send the URL of the page where the widget loads. If the widget sits inside an iframe, send the iframe's URL. The hostname in the check has to be the site's.
  • Score below the site's threshold. If everything above is right and it still fails, you have a reputation problem (IP, browser, behaviour). That's covered in why your reCAPTCHA v3 score is low.
  • Enterprise site rules. An Enterprise site can reject a valid token on its own rules, based on reasons, IP or session. Test against your real target, not only a demo.

FAQ

Is reCAPTCHA Enterprise the same as v3?
No. Enterprise is the Google Cloud product. Its score keys work like v3 in the browser, but they load enterprise.js, call grecaptcha.enterprise.execute(), and are checked through the assessment API, which returns reasons and token details along with the score. Enterprise also has checkbox and invisible keys that behave like v2.

How do I know if a site uses reCAPTCHA Enterprise?
Find the sitekey the form uses. Then check whether enterprise.js loads that key, or whether it's passed to grecaptcha.enterprise.*. An enterprise.js somewhere on the page isn't enough on its own, because it can belong to another key.

Does render=explicit mean v3?
No. render=explicit is v2's explicit rendering mode. v3 is render= followed by the sitekey.

Can I tell the version from the sitekey?
No. All variants use 40-character keys that start with 6L.

Can I ask the solver for a 0.9 score?
No. There's no min_score parameter. The site decides which score it accepts.


I'm Bassem, and I run CaptchaAI. ocr.captchaai.com in the scripts above is our endpoint, and it solves all five variants in the table through the 2Captcha-compatible API. To try the detector's output against your own target, a free thread is enough: one thread for 30 days, no card. The v3 Enterprise guide lists every parameter.

Top comments (0)