Both are reCAPTCHA v2 variants using the same underlying technology. The checkbox version shows "I'm not a robot" and may present image grids. The invisible version triggers automatically on user actions (button click, form submit, or page load) with no visible widget. For CaptchaAI solving, the only difference is adding invisible=1 to your request — but the token injection process differs significantly.
Side-by-side comparison
| Feature | v2 Checkbox | v2 Invisible |
|---|---|---|
| Visible widget | Yes — "I'm not a robot" checkbox | No — hidden from users |
| Trigger mechanism | User clicks checkbox | Button click, form submit, or page load |
| Image challenge | Shown when Google suspects a bot | Popup in bottom-right corner when suspicious |
| User experience | Moderate friction | Low/zero friction |
| Implementation | Add g-recaptcha div to page |
Add data-callback to button or grecaptcha.execute() |
| Sitekey format | Same as invisible | Same as checkbox |
| Token field | g-recaptcha-response |
g-recaptcha-response |
| Callback function | Optional | Almost always required |
| CaptchaAI method | method=userrecaptcha |
method=userrecaptcha + invisible=1 |
| Token lifetime | 120 seconds | 120 seconds |
| Solve time | 10-30 seconds | 10-25 seconds |
How each appears in the HTML
v2 Checkbox
<!-- Standard checkbox widget -->
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
data-callback="onSubmit">
</div>
<!-- Widget renders as: -->
<!-- [✓] I'm not a robot reCAPTCHA logo -->
The user sees and interacts with the checkbox. If Google is suspicious, an image grid challenge appears inline.
v2 Invisible
<!-- Pattern 1: Invisible widget on a button -->
<button class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
data-callback="onSubmit"
data-size="invisible">
Submit
</button>
<!-- Pattern 2: Invisible div (programmatic trigger) -->
<div class="g-recaptcha"
data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
data-size="invisible"
data-callback="onSubmit">
</div>
<!-- Pattern 3: Programmatic render -->
<script>
grecaptcha.render('submit-btn', {
sitekey: '6Le-wvkSAAAAAPBMRTvw...',
callback: onSubmit,
size: 'invisible'
});
</script>
No visible widget. reCAPTCHA activates when the user triggers the specified element or when grecaptcha.execute() is called.
How to detect which variant is on a page
Python detection:
import requests
from bs4 import BeautifulSoup
import re
def detect_recaptcha_variant(url):
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser")
# Check for invisible indicators
invisible_widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
if invisible_widget:
return {
"variant": "invisible",
"sitekey": invisible_widget.get("data-sitekey"),
"callback": invisible_widget.get("data-callback")
}
# Check for programmatic invisible in scripts
for script in soup.find_all("script"):
if script.string and "invisible" in str(script.string):
key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
if key_match:
return {
"variant": "invisible-programmatic",
"sitekey": key_match.group(1),
"callback": "check grecaptcha.render() call"
}
# Check for standard checkbox
checkbox_widget = soup.find(class_="g-recaptcha")
if checkbox_widget:
return {
"variant": "checkbox",
"sitekey": checkbox_widget.get("data-sitekey"),
"callback": checkbox_widget.get("data-callback")
}
return None
result = detect_recaptcha_variant("https://example.com/login")
print(result)
Node.js detection:
const axios = require("axios");
const cheerio = require("cheerio");
async function detectRecaptchaVariant(url) {
const { data } = await axios.get(url);
const $ = cheerio.load(data);
// Check for invisible
const invisible = $(".g-recaptcha[data-size='invisible']");
if (invisible.length) {
return {
variant: "invisible",
sitekey: invisible.attr("data-sitekey"),
callback: invisible.attr("data-callback"),
};
}
// Check scripts for programmatic invisible
const scripts = $("script")
.map((_, el) => $(el).html())
.get()
.join("\n");
if (scripts.includes("invisible")) {
const keyMatch = scripts.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
if (keyMatch) {
return {
variant: "invisible-programmatic",
sitekey: keyMatch[1],
callback: "check render call",
};
}
}
// Check for standard checkbox
const checkbox = $(".g-recaptcha");
if (checkbox.length) {
return {
variant: "checkbox",
sitekey: checkbox.attr("data-sitekey"),
callback: checkbox.attr("data-callback"),
};
}
return null;
}
Quick browser console check:
const el = document.querySelector('[data-size="invisible"]');
console.log(el ? "Invisible reCAPTCHA" : "Checkbox reCAPTCHA");
| Detection signal | v2 Checkbox | v2 Invisible |
|---|---|---|
data-size="invisible" |
Not present | Present |
| Visible checkbox on page | Yes | No |
grecaptcha.execute() called |
No (user clicks) | Yes (programmatic) |
| Challenge popup position | Inline, below checkbox | Bottom-right corner of page |
Solving with CaptchaAI
v2 Checkbox
import requests
import time
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": "6Le-wvkSAAAA...",
"pageurl": "https://example.com/form"
})
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY", "action": "get", "id": task_id
})
if result.text.startswith("OK|"):
token = result.text.split("|")[1]
break
v2 Invisible
import requests
import time
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": "6Le-wvkSAAAA...",
"pageurl": "https://example.com/form",
"invisible": 1 # Only parameter difference
})
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY", "action": "get", "id": task_id
})
if result.text.startswith("OK|"):
token = result.text.split("|")[1]
break
Token injection — the critical difference
v2 Checkbox: Hidden field is usually enough
# Selenium — inject into hidden field
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
# If the page uses a callback, also call it
callback = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
if callback:
driver.execute_script(f'{callback}("{token}");')
v2 Invisible: Callback is almost always required
# Selenium — inject AND call the callback
driver.execute_script(
f'document.getElementById("g-recaptcha-response").value = "{token}";'
)
# CRITICAL: Invisible reCAPTCHA almost always requires calling the callback
callback_name = driver.find_element(
"css selector", ".g-recaptcha[data-size='invisible']"
).get_attribute("data-callback")
driver.execute_script(f'{callback_name}("{token}");')
// Puppeteer — invisible callback injection
await page.evaluate((tok) => {
// Set the hidden field
document.getElementById("g-recaptcha-response").value = tok;
// Find and call the callback function
const widget = document.querySelector("[data-size='invisible']");
const cbName = widget?.getAttribute("data-callback");
if (cbName && typeof window[cbName] === "function") {
window[cbName](tok);
}
}, token);
Key difference: v2 checkbox forms often work with just the hidden field injection because the user's click on the checkbox already registered the callback. Invisible reCAPTCHA never has that click — the callback must be called explicitly.
Universal solver for both variants
import requests
import time
from bs4 import BeautifulSoup
class RecaptchaV2UniversalSolver:
def __init__(self, api_key):
self.api_key = api_key
def detect_and_solve(self, page_url, page_html=None):
if not page_html:
page_html = requests.get(page_url).text
soup = BeautifulSoup(page_html, "html.parser")
# Detect variant
invisible = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
widget = invisible or soup.find(class_="g-recaptcha")
if not widget:
raise Exception("No reCAPTCHA widget found")
sitekey = widget.get("data-sitekey")
is_invisible = invisible is not None
callback = widget.get("data-callback")
params = {
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url
}
if is_invisible:
params["invisible"] = 1
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key, "action": "get", "id": task_id
})
if result.text.startswith("OK|"):
return {
"token": result.text.split("|")[1],
"variant": "invisible" if is_invisible else "checkbox",
"callback": callback,
"sitekey": sitekey
}
if result.text != "CAPCHA_NOT_READY":
raise Exception(f"Solve error: {result.text}")
raise Exception("Timed out")
# Usage
solver = RecaptchaV2UniversalSolver("YOUR_API_KEY")
result = solver.detect_and_solve("https://example.com/login")
print(f"Variant: {result['variant']}, Callback: {result['callback']}")
Troubleshooting
| Problem | Checkbox fix | Invisible fix |
|---|---|---|
| Token not accepted | Inject into g-recaptcha-response field |
Inject AND call the data-callback function |
| Widget not found | Look for .g-recaptcha class |
Check for data-size="invisible" or script render calls |
| Form submits but fails | Check if callback is expected | Callback is almost always required — find and call it |
| Page reloads after injection | JavaScript validation failed | Call callback before form auto-submission triggers |
FAQ
Is invisible reCAPTCHA harder to solve?
No. The solving process through CaptchaAI is nearly identical. Adding invisible=1 to your request is the only change. The extra challenge is on the injection side: you must find and execute the callback function.
Do both use the same sitekey format?
Yes. The sitekey format is identical. You cannot tell the variant from the sitekey alone — check for data-size="invisible" in the HTML.
Does invisible reCAPTCHA still show image challenges?
Yes. When Google detects suspicious behavior, invisible reCAPTCHA shows an image challenge popup in the bottom-right corner of the page. This is why you still need CaptchaAI — the invisible version can still present challenges.
Can a site switch between checkbox and invisible?
Yes. This is a configuration change on the site's end — same sitekey, different implementation. Some sites use checkbox on desktop and invisible on mobile. Always detect the variant dynamically.
Which is faster to solve?
Invisible solves are typically slightly faster (10-25 seconds vs 10-30 seconds) because they tend to trigger fewer image challenges. But the difference is marginal.
Related guides
- How to Solve reCAPTCHA v2 Using API — full v2 checkbox tutorial
- How reCAPTCHA Invisible Works — invisible mechanism explained
- How to Solve reCAPTCHA Invisible Using API — invisible solving tutorial
- Common reCAPTCHA Invisible Errors and Fixes — invisible troubleshooting
- How to Solve reCAPTCHA Invisible Using API
- How reCAPTCHA Invisible Works
- reCAPTCHA v2 vs v3 Explained
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.