CAPTCHA Types

How to Identify Which CAPTCHA a Page Uses and Find Its Sitekey

You cannot submit a solver task until you know three things about the page: which challenge it serves, the sitekey that identifies the site to that challenge provider, and where the resulting token has to go. All three are visible from the browser. Open DevTools, look at the script hosts the page loads — the host names the vendor — then read the sitekey off the widget element, and if it is not there, off the widget's own iframe request in the Network tab. On a straightforward page it is a two-minute job.

The parts that waste an afternoon are the ones that follow: telling reCAPTCHA v3 from v2 when neither renders a checkbox, finding a key that JavaScript injects at runtime and that never appears in view-source, and realising the page is serving a Cloudflare interstitial, which has no sitekey at all because it is not a widget. This walks the whole path, in the order you should do it.

One prerequisite that is not optional: do this against a site you own, a staging environment, or a target whose terms and robots.txt permit automated access. The techniques below are the same ones you use to debug your own login page; the legitimacy comes from the target, not the tooling.

Step 1 — Confirm what you are actually looking at

Load the page in a normal browser profile with DevTools open and note which of these you see.

  • An embedded widget — a checkbox, a "verify you are human" box, a puzzle. The page is yours to inspect, and it has a sitekey.
  • Nothing at all, until you submit. Invisible reCAPTCHA v2 and v3 run without a visible element. The widget is still there; the trigger is the submit handler.
  • A full-page interstitial — a branded waiting screen that reloads into the site. That is a challenge page, not a widget. Cloudflare's Challenge is the common case, and it returns a clearance cookie rather than a token, so there is no sitekey to find. Our Turnstile explainer covers the difference, which trips up more integrations than any other distinction in this space.
  • A challenge only sometimes. If the page is clean from your desk but challenges your workers, the trigger is the network context or the fingerprint, not the page. Reproduce it through the same proxy your automation uses before you conclude anything about the challenge type.

Note the exact URL where the challenge renders. Not the site root — the specific page. Solver APIs bind the task to a page URL, and a key submitted with the wrong URL fails in a way that looks like a solver problem.

Step 2 — Identify the vendor from the script host

The vendor is never a guess. Open the Network tab, filter to JS, and reload. The host that serves the challenge script names the product.

Script or request host What it is
www.google.com/recaptcha/api.js (or www.recaptcha.net) reCAPTCHA v2 or v3
www.google.com/recaptcha/enterprise.js reCAPTCHA Enterprise
challenges.cloudflare.com/turnstile/v0/api.js Cloudflare Turnstile
/cdn-cgi/challenge-platform/… on the site's own domain Cloudflare Challenge — interstitial, not a widget
js.hcaptcha.com/1/api.js hCaptcha
static.geetest.com/… (gt.js, gcaptcha4.js) Geetest v3 or v4
client-api.arkoselabs.com/v2/… Arkose / FunCaptcha
An image served from the site itself, no third-party script Classic image CAPTCHA

The same list from the console, when you want it in one line:

[...document.querySelectorAll('script[src], iframe[src]')]
  .map(el => el.src)
  .filter(src => /recaptcha|turnstile|hcaptcha|geetest|arkose|awswaf|challenge-platform/i.test(src));

If nothing matches and the page shows a distorted image with a text box, you are on a classic image CAPTCHA. There is no key: you send the image bytes, fetched inside the same session that will post the form, and you get a string back. Every CAPTCHA type sets out what each class returns and why that changes the integration.

Step 3 — Pin down the version, because v2 and v3 are different tasks

For reCAPTCHA, the vendor is only half the answer. Read the script URL and the widget element together.

  • api.js?render=<a long key> — that is v3. The key in render is the sitekey. There is no checkbox; the page calls grecaptcha.execute() on an event.
  • api.js with no render, or render=explicit, plus a div.g-recaptcha carrying data-sitekey — that is v2.
  • data-size="invisible" on that div — v2 invisible. Same task type as v2, but the token is consumed by a callback rather than a visible tick.
  • enterprise.js, or grecaptcha.enterprise referenced in page JS — Enterprise. It comes in v2 and v3 flavours, and solver APIs treat it as its own parameter, so record it separately.

v3 needs one more thing: the action string. The page passes it to grecaptcha.execute(), and the site's backend scores the token partly on whether the action matches what it expected. Search the page sources for execute( and read the object beside the key:

grecaptcha.execute('6Lc…', { action: 'login' });

Submit a v3 task without that action and you will get a token back that the site quietly scores badly — a parameter bug that presents as a solve-quality problem.

For Turnstile, look at the div.cf-turnstile attributes: data-sitekey, and sometimes data-action and data-cdata, both of which the origin may check. For Geetest, v3 exposes a gt identifier plus a per-session challenge value fetched from the site's own endpoint, while v4 uses a single captchaId — so for v3 you must fetch a fresh challenge for every attempt rather than caching it.

Step 4 — Read the sitekey off the element

On a page that renders its widget in the HTML, this is a one-liner:

document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey');

Two shape cues help you sanity-check what you found, though neither is a guarantee: reCAPTCHA keys conventionally begin 6L, and Turnstile keys conventionally begin 0x4. A "key" that looks like neither is often a container id you have grabbed by mistake.

If the page has more than one widget — a login form and a newsletter box, say — querySelector gives you the first one, which may not be yours. Enumerate them and match by their position in the form you actually submit:

[...document.querySelectorAll('[data-sitekey]')]
  .map(el => ({ key: el.dataset.sitekey, id: el.id, html: el.outerHTML.slice(0, 120) }));

Step 5 — When the sitekey is not in the HTML

This is the case that sends people to Stack Overflow, and it has three reliable answers.

Look at the rendered DOM, not the source. View-source shows the server's HTML; the widget is often injected afterwards by JavaScript. The Elements panel and the console snippets above read the live DOM, which is where an injected widget exists.

Read it out of the widget's own iframe request. Every widget loads an iframe, and the sitekey travels in that URL. For reCAPTCHA, filter the Network tab for anchor and look at the query string: the k parameter is the sitekey, and the path tells you whether you are on the standard or the enterprise endpoint. Turnstile's widget iframe carries its key in the URL too — select the iframe in Elements and read its src. This works even when the key never appears in any script you can read.

Ask the page's own runtime. For reCAPTCHA, the page keeps its widget configuration in a global that you can walk when the DOM has told you nothing:

Object.entries(___grecaptcha_cfg.clients)
  .map(([id, client]) => [id, JSON.stringify(client).match(/6L[\w-]{20,}/)?.[0]]);

Treat that as a debugging aid rather than a stable interface — the internal shape is undocumented and changes. The iframe URL is the more durable source.

Step 6 — Find where the token has to go

Knowing the key gets you a token. Knowing the injection target is what makes the submission succeed.

  • reCAPTCHA v2: a hidden textarea#g-recaptcha-response. Set its value, then submit.
  • Turnstile: a hidden input[name="cf-turnstile-response"].
  • hCaptcha: h-captcha-response.
  • Invisible v2 and v3: frequently no field at all. The page passes the token to a JS callback — check data-callback on the widget, or the function called after execute() resolves — and the token ends up in a JSON body rather than a form field.

When you cannot find the field, stop guessing and watch a real submission. Solve the challenge by hand once with the Network tab recording, then read the request payload the page sent. Whatever carries the token there is your target, and its name is the parameter your automation must set. That single observation also tells you whether the site posts a form or a JSON body, which changes the shape of your request.

Step 7 — Write the facts down before you code

By this point you should have a short config block, and it is worth storing it as data rather than scattering it through code:

  • Page URL where the challenge renders, exactly.
  • Challenge type and version, including the enterprise and invisible flags.
  • Sitekey, plus the action string for v3 and any cdata/action for Turnstile.
  • Injection target — field name, or the callback the page uses.
  • Whether the challenge needs a proxy to reproduce at all, and if it is cookie-based, the user agent that earned the clearance.

Those are exactly the parameters a solver task takes, which is why gathering them first turns integration into plumbing. The submit, poll and inject walkthrough picks up from here with the request shapes.

What breaks this in production

  • Sitekeys differ per environment. Staging and production usually carry different keys, and hardcoding the one you found while debugging is a classic Friday incident.
  • Keys rotate. Rarely, but they do. Fail loudly on an invalid-key error rather than retrying it, because retrying a wrong key just spends budget.
  • The widget varies by path or locale. A site can serve v2 on one form and v3 site-wide, or a different key per region. Verify on the exact page and locale your automation hits.
  • The challenge appears only under some conditions. If it shows for your workers and not your laptop, that is a reputation or fingerprint signal, not a page change.
  • The interstitial is not a widget. No sitekey, no token — you need the clearance cookie and the matching user agent, together.

FAQ

Is the sitekey secret? No. It is public by design, embedded in the page so the widget can identify the site. The secret half lives on the site's backend and is used to verify the token. Finding a sitekey is reading published markup, not extracting a credential.

How do I tell reCAPTCHA v2 invisible from v3? Both are invisible, so read the script tag. A render= parameter carrying the key means v3. No render parameter, plus a div.g-recaptcha with data-size="invisible", means v2 invisible. They are different task types with different parameters.

The page loads reCAPTCHA but I never see a challenge. Do I still need to solve it? Usually yes. Invisible and v3 implementations still require a token in the submission; the site simply never showed you a puzzle. If the form fails without one, the token is being checked.

Why does my task fail when the sitekey is definitely correct? The three usual causes are a page URL that does not match where the widget renders, a missing v3 action, and a missing enterprise flag. All three produce failures that look like solver quality problems.

What if the site uses a CAPTCHA I do not recognise? Search the network requests for a third-party host and look it up. If it serves no third-party script, it is almost certainly a self-hosted image CAPTCHA — send the image bytes from the same session and expect a text answer back.

Do I need a headless browser just to find these values? No. Finding them is a manual, one-time job in a normal browser. A browser only enters the picture if the automation itself has to render the widget.


Vendor, version, sitekey, injection target: gathered once, they turn a challenge from a mystery into a parameter list. When you are ready to hand those parameters to a solver and get a token back, the endpoints and language examples are at CaptchaAI.

Comments are disabled for this article.