How reCAPTCHA v3 Scores Browser Automation (and Why IP Trust + Persistent Profiles Beat Fake Mouse Movement)
Your nightly Playwright synthetic checkout monitor runs fine on your laptop at 4 p.m. At 2 a.m., the scheduled GitHub Actions job (AS8075 Microsoft Azure) or AWS runner (AS16509 Amazon) fills out the exact same checkout form, clicks submit, and gets rejected. When you pull the backend logs, the /recaptcha/api/siteverify payload tells the whole story: {"success": true, "score": 0.1, "action": "checkout"} against a 0.7 production threshold.
Someone on the team spends half a day bolting on a Bezier-curve mouse-jitter plugin and randomizing keystroke delays between 45 and 130 milliseconds. They re-run the pipeline. The score is still 0.1.
I see QA and synthetic monitoring teams burn days on cursor math every month. Here is why simulated mouse wiggles barely register when the underlying network and profile signals are wrong, and what actually moves a Playwright session from 0.1 to 0.7+.
What actually drives a 0.1 vs 0.9 score
Unlike v2, reCAPTCHA v3 never pauses the page to ask which squares contain a motorcycle. It runs silently in the background when the page calls grecaptcha.execute(siteKey, {action: 'checkout'}), bundles environment and network telemetry into an encrypted token, and returns a discrete score (0.1, 0.3, 0.7, or 0.9) to your server.
Across hundreds of automated QA runs, four concrete signals dominate that calculation:
- ASN and subnet reputation. This is the heaviest starting weight. Traffic exiting from cloud hosting subnets (
AS16509Amazon,AS8075Microsoft,AS15169Google Cloud) starts in a deep hole before a single DOM event fires. Google knows real shoppers do not check out from ephemeral Azure build containers, the exact failure mode behind why scrapers and QA jobs fail on GitHub Actions and cloud runners. By contrast, traffic arriving from physical US mobile carriers like T-Mobile (AS21928), Verizon Wireless (AS22394), or AT&T Mobility (AS20057) exits through Carrier-Grade NAT (CGNAT) gateways shared by hundreds of active smartphones on the same tower. You can check which ASN your test runner exits from with our ASN lookup tool. - Persistent
_GRECAPTCHAstate inuserDataDir. Callingbrowser.new_context()in Playwright creates a sterile browser profile on every run. Every session requests a freshanchoriframe and writes a brand-new_GRECAPTCHAentry intolocalStorageand cookies underwww.google.com/recaptcha. Real browsers carry aged storage and prior token history. Mounting a persistentuserDataDiracross test runs so_GRECAPTCHAstate carries over often lifts a score from0.3to0.7without changing a line of interaction code. - Browser runtime and GPU renderer strings. Headless Linux runners without a GPU fall back to software rendering via Google SwiftShader (
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device))). Pair SwiftShader with a UTC container timezone,C.UTF-8locale, or a WebRTC local IP leak, and the runtime fingerprint screams automation. Run your context against the proxy & WebRTC leak checker and walk through our Playwright + Cloudflare diagnostic checklist to catch environment mismatches early. - Action timing relative to page lifecycle. Triggering
grecaptcha.execute()90 milliseconds afterDOMContentLoaded(before web fonts, layout shifts, or focus events settle) looks programmatic no matter how many syntheticmousemoveevents you fire first.
Why rotating proxies break v3 token verification
When engineers realize cloud ASNs are tanking their v3 score, their first instinct is often to wire up a per-request rotating proxy pool. That usually makes the score worse.
Watch the network tab during a real reCAPTCHA v3 lifecycle:
- First, the page loads
/recaptcha/api.jsand initializes the invisible/recaptcha/api2/anchoriframe. - Second, when the user triggers the checkout action, the iframe POSTs encrypted telemetry to
/recaptcha/api2/reloadto mint a short-lived token (valid for two minutes). - Third, your application submits that token to your backend, which verifies it (often passing the client
remoteip) againsthttps://www.google.com/recaptcha/api/siteverify.
If a rotating proxy changes your exit IP between the anchor load, the reload request, and the form submission, or if a cached _GRECAPTCHA token minted on a cable ISP in Ohio suddenly appears from a different subnet in Texas ten seconds later, Google flags the continuity break and returns 0.1.
For v3 flows, you need a sticky 4G/5G mobile session (sticky_10 or sticky_30) that pins your browser context to a single carrier CGNAT gateway for 10 to 30 minutes. We covered the session mechanics in sticky vs rotating mobile proxies, and you can inspect our active US carrier modems on the live pool page.
A Playwright setup that holds a 0.7+ score in synthetic monitoring
Here is the Python pattern we use for production synthetic checkout monitoring. It combines launch_persistent_context (preserving _GRECAPTCHA storage across runs) with a 10-minute sticky T-Mobile session and an ASN preflight gate:
import json
from pathlib import Path
from playwright.sync_api import sync_playwright
PROXY_URL = "http://USER-isp_tmobile-state_ny-sticky_10:PASS@us-mob.veilsocks.io:9200"
PROFILE_DIR = Path("/var/tmp/qa-playwright-profile-ny")
def parse_proxy(url: str) -> dict:
creds, hostport = url.replace("http://", "").split("@", 1)
username, password = creds.split(":", 1)
return {"server": f"http://{hostport}", "username": username, "password": password}
def run_checkout_monitor():
PROFILE_DIR.mkdir(parents=True, exist_ok=True)
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir=str(PROFILE_DIR),
headless=True,
proxy=parse_proxy(PROXY_URL),
locale="en-US",
timezone_id="America/New_York",
viewport={"width": 1366, "height": 768},
args=["--disable-blink-features=AutomationControlled"],
)
page = context.pages[0] if context.pages else context.new_page()
# 1. Preflight: verify carrier ASN before touching the target checkout
page.goto("https://blog.veilsocks.com/api/asn-lookup", wait_until="domcontentloaded")
meta = json.loads(page.inner_text("body"))
assert meta.get("asn") in ("AS21928", "AS22394", "AS20057"), f"Unexpected ASN: {meta}"
# 2. Load checkout, allow lifecycle settle, then interact naturally
page.goto("https://shop.example.com/checkout", wait_until="networkidle")
page.wait_for_timeout(1800)
page.locator("#billing-email").click()
page.locator("#billing-email").fill("qa-monitor@example.com")
page.wait_for_timeout(600)
page.locator("button[type='submit']").click()
page.wait_for_load_state("networkidle")
context.close()
if __name__ == "__main__":
run_checkout_monitor()
In CI, cache /var/tmp/qa-playwright-profile-ny across workflow runs using actions/cache (keyed by runner OS and target region) so the profile ages naturally instead of starting cold every night.
When to skip proxies altogether and use Google's test keys
Before routing every test suite through a mobile proxy, ask what environment you are actually testing. If you are running functional regression tests against a staging or preview environment that your team owns, do not use a proxy.
Google provides an official reCAPTCHA test sitekey (6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI) and test secret (6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe) built specifically for CI pipelines, or you can configure your staging WAF and backend to bypass score checks when an authenticated internal QA header is present. That approach is free, deterministic, and zero-maintenance.
Save persistent browser profiles and sticky 4G/5G mobile proxies for the workloads where test keys are off the table: production synthetic monitoring against live checkout flows, ad-verification audits, or authorized QA against third-party portals where the live reCAPTCHA v3 configuration must remain intact.
Questions I get
Does fake mouse movement improve a reCAPTCHA v3 score in Playwright? Rarely on its own. If your browser exits from a cloud datacenter ASN (AS16509 or AS8075) with a blank profile, synthetic Bezier-curve cursor movement will still score 0.1. Carrier IP reputation and persistent _GRECAPTCHA storage carry far more weight than simulated mouse jitter.
Why does my reCAPTCHA v3 score drop when using rotating proxies? reCAPTCHA v3 ties the initial anchor load, the reload token request, and the backend /recaptcha/api/siteverify check together. If your exit IP changes mid-page or mid-session, Google flags the IP mismatch and drops the score. Use a 10-to-30-minute sticky mobile session (sticky_10 or sticky_30) instead.
Should I use a mobile proxy or Google's test sitekey for CI/CD testing? On staging environments you control, use Google's official test sitekey (6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI) or an internal allowlist header so CI tests always pass without proxy overhead. Use a persistent Playwright profile and a sticky 4G/5G mobile proxy when running production synthetic monitoring where live reCAPTCHA keys must stay active.
Real 4G/5G mobile proxies, your GB never expire
Genuine US carrier SIM IPs — SOCKS5 & HTTP, pay per GB, instant activation.
Create your account →