How to Improve reCAPTCHA v3 Score: Six Fixes That Work

improve recaptcha v3 score - How to Improve reCAPTCHA v3 Score: Six Fixes That Work

You can’t set a reCAPTCHA v3 score. Google assigns it per request, and nothing in your code changes it directly. What you can change is what feeds it: how you name actions, when you request the token, how much of your site runs reCAPTCHA at all, and what your backend does with the result. Fix those and the distribution moves.

Six changes, ordered by how much they typically help. Measure first, because half the sites that think they have a scoring problem have a threshold problem.

What the score actually means

v3 returns a number between 0.0 and 1.0 with every verification. Google’s wording: 1.0 is very likely a good interaction, 0.0 is very likely a bot. There’s no checkbox and no puzzle, so the number is the entire signal.

Two things follow from that, and both matter:

  • The score is per request and per action, not per user. The same visitor can score 0.9 on your homepage and 0.3 on checkout.
  • Google’s suggested starting threshold is 0.5. That’s a default to tune away from, not a target to hit.

If you’re new to how v3 differs from the checkbox version, our explainer on how reCAPTCHA works covers the mechanics.

Measure before you change anything

The reCAPTCHA admin console shows a score distribution for your site and a breakdown for your top ten actions. Look at it before touching code. You’re looking for one of three shapes:

  • Everything at 0.9, and you’re still blocking people. Your threshold or your backend logic is the problem, not the score.
  • A wide spread with a bump at the low end. Normal. Tune the threshold per action.
  • Everything at 0.1 to 0.3. Something structural is wrong. Usually the token, not the traffic.

Google also warns that scores in staging or right after you install v3 differ from production, because the model has no history for the site yet. Give it a week of real traffic before drawing conclusions. To sanity-check a single request in isolation, our live reCAPTCHA v3 test page returns the raw score for one solve.

Fix 1: name your actions, and name them correctly

This is the biggest single win, and it’s routinely skipped. Google scores each action separately and uses the action’s own history as context. One generic action across the whole site means one blended history, and every page inherits the worst of it.

// One action per meaningful event. Not one for the whole site.
grecaptcha.ready(function () {
  grecaptcha.execute("YOUR_SITEKEY", { action: "login" })
    .then(function (token) {
      document.getElementById("recaptcha-token").value = token;
    });
});

Rules Google enforces: actions may contain only alphanumeric characters, slashes and underscores, and they must not be user-specific. So checkout/payment is fine, checkout_user_8842 is not. A user-specific action fragments the history into thousands of buckets with no data in any of them, which is worse than not naming actions at all.

Fix 2: run reCAPTCHA on more than the form

v3 scores behaviour, and behaviour needs more than one data point. If the script only loads on your login page, Google sees a visitor who materialises at a form and submits, which is exactly what a bot looks like.

Google’s own recommendation is to load v3 across the site, including pages with no form on them. You don’t have to verify on those pages. Just executing the script gives the model something to work with by the time the visitor reaches the action you care about.

This is also the fix people accidentally undo. Moving the script into a conditional that only fires on the checkout route will drop your scores within days.

Fix 3: get the token when you submit, not on page load

reCAPTCHA v3 tokens expire two minutes after they’re issued. Generate one in ready() at page load, and any visitor who reads your form for longer than that submits a dead token. Depending on how your backend handles the failure, that reads as a bad score or a hard rejection.

// Solve on submit so the token is always fresh.
form.addEventListener("submit", function (e) {
  e.preventDefault();
  grecaptcha.execute("YOUR_SITEKEY", { action: "login" })
    .then(function (token) {
      tokenField.value = token;
      form.submit();
    });
});

Long forms, multi-step checkouts and anything with a file upload are where this bites hardest.

Fix 4: verify server side, and check the action too

The token is worthless until your backend exchanges it. That exchange returns the score, the action, and a hostname, and you should be checking all three.

# Exchange the token for the score. Server side only.
curl -X POST https://www.google.com/recaptcha/api/siteverify \
  -d secret=YOUR_SECRET_KEY \
  -d response=THE_TOKEN_FROM_THE_PAGE

# {"success":true,"score":0.9,"action":"login","hostname":"example.com"}

If you only check success, you’re not using v3 at all. success means the token parsed, not that the visitor looked human. And if you don’t compare action against what that endpoint expected, a token minted on your low-value newsletter form works fine on your login endpoint.

Fix 5: set a threshold per action, not one for the site

A checkout and a newsletter signup shouldn’t share a cutoff. Once you have a week of data per action, set each one where your traffic actually sits.

Action typeReasonable starting pointWhat to do below it
Newsletter, search, page view0.3Allow, log the score
Login, comment0.5Add a second factor or a v2 checkbox
Checkout, password reset0.7Step up to a manual challenge

Notice that none of those rows say “block”. Hard-blocking on a low score is how v3 deployments lock out real customers on corporate VPNs and privacy browsers. Step up the challenge instead.

Fix 6: rule out the usual score killers

If the structural fixes are all in place and scores are still low, work through these:

CauseWhy it scores low
Two reCAPTCHA scripts on one pageThe second load clobbers the first and the token binds to the wrong context
Shared or datacenter IPsOffice NAT, VPNs and cloud egress all carry other people’s history
Aggressive privacy extensionsBlocked cookies and storage leave the model with nothing to read
Iframed or embedded formsCross-origin context weakens the signal
Sitekey and domain mismatchCheck the hostname field in the verify response

One thing that is not on this list: hiding the badge. That’s a CSS and attribution question, and it has no effect on scoring. We covered the compliant way to do it in hiding the reCAPTCHA v3 badge.

The problem tuning won’t solve

If the traffic is automated, it scores low because it is what v3 was built to detect. No amount of action naming fixes a headless browser. For testing your own site, or for automation you’re authorised to run, you get a token from a solver instead of from the page:

# pip install capskip
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
    version="v3",
    action="login",     # must match the page
)

print(result["code"])   # token, inject it and submit

Note the action again. It matters as much on this side as it does on yours, for exactly the same reason: the backend compares it.

What you can’t do is name a score. There’s no minimum-score parameter on the solve request. The number is Google’s call, made when your token is verified, so a solver hands you a token and nothing more.

Frequently asked questions

Why is my reCAPTCHA v3 score always 0.1?

A flat 0.1 across all traffic is almost never a behaviour problem. Check for a duplicate script tag, a stale token issued more than two minutes before submission, or a sitekey registered to a different domain. The hostname field in the verify response settles the last one immediately.

How long does it take for score changes to show up?

Several days. The model uses history per site and per action, so a new action starts with no context and settles as traffic accumulates. Don’t judge a change on one afternoon of data.

Does a higher score threshold make my site safer?

Only up to a point, and it costs you real users. Raising every endpoint to 0.9 blocks people on shared IPs and privacy browsers long before it stops a determined attacker. Set the threshold per action and step up the challenge instead of rejecting outright.

Can I see the score without writing backend code?

Yes. The admin console shows the distribution for your own site, and our v3 demo page returns the raw score for a single solve so you can compare one request against what your own endpoint reports.

Summary

Name one action per event, load v3 across the site, mint the token at submit time, verify server side and compare the action, then set a threshold per endpoint instead of one global cutoff. Check the admin console a week later, not the same day.

For the mechanics of v3 scoring and the options that go with it, see reCAPTCHA v3 solving, and Google’s own v3 documentation is the authority on thresholds and action naming. If you’re testing your own forms against a low score, CapSkip is a captcha solver that runs locally, so you can generate tokens all day without a per-solve bill.