Checkout Bouncer

Notes

Why a low reCAPTCHA score is not proof of a bot

A low reCAPTCHA v3 score is doubt, not proof. Why real customers score low, and why checkout should fail open when Google is slow.

4 min read

A customer emails to say checkout refused their order. You check the log and see a reCAPTCHA v3 score of 0.3. It is tempting to decide the customer is lying or is a bot. Usually neither is true.

A score is a guess

reCAPTCHA v3 returns a number between 0.0 and 1.0 based on signals Google collects in the browser. Google does not tell you which signals it used or why a visitor got a particular score. You get the number, and you decide what to do with it.

That makes a low score a statement of doubt, not a finding. It says the visitor looked unusual to Google at that moment. Plenty of real people look unusual.

Who gets low scores without being a bot

The pattern is consistent: visitors who give Google less to go on tend to score lower. That includes:

  • People on privacy browsers or strict tracking protection, which limit what Google's script can see.
  • People on VPNs, corporate networks, or shared mobile IP addresses that many other users also come from.
  • People with ad blockers or script blockers that interfere with Google's script.
  • People who move fast. A returning customer with autofill can complete checkout in a few seconds, which looks a lot like a script.
  • People on a new device or a cleared browser, with no history Google can use.

None of these are rare among online shoppers. So any threshold you set will refuse some real customers. The question is how many, and what happens to them.

Treat a low score as one signal

A low score on its own is weak evidence. A low score together with other signs is much stronger. When you review a blocked checkout, look at the whole picture:

  • Did the same IP address or email make many attempts in a short time?
  • Was the cart a single cheap item, like most card-testing orders?
  • Did the request reach the checkout endpoint without the page being loaded first?
  • Did the token pass the other checks, such as the right action name and your own hostname?

If the answer to all of these is no, you probably blocked a person. Tell them so politely, ask them to try again or try another browser, and consider whether your threshold is too strict.

When you block, show a clear message that tells a real person what to do. "We could not verify this checkout, please refresh and try again" helps. A blank error helps nobody.

What happens when Google does not answer

There is a second way a score-based check can fail a real customer. Your server has to call Google to verify each token. That call can be slow, time out, or fail because of a network problem on your host or an outage at Google.

When that happens, your code has to choose. It can refuse the checkout (fail closed), or let it through without a score (fail open).

Fail closed sounds safer, but think about what it means. During a Google problem, every customer on your store gets refused, and your checkout is down for as long as the problem lasts. You have turned an outside outage into your own outage.

Fail open keeps your store selling. A bot gets a short window with no score check, but your other layers still work: rate limits, gateway fraud rules, and the bot's own need to get past the rest of checkout.

For most stores, fail open is the better trade. A few minutes of reduced protection costs less than a few minutes of no sales.

Make the timeout short

Fail open only helps if your code gives up quickly. If the verify call waits 30 seconds before it times out, every customer waits 30 seconds at Place order, and many will leave or click twice. Set a short timeout on the request to Google, and log every case where the check fell back, so you can see when it happens and how often.

A spike of fallback events in the log is also worth a look. It can mean Google had a problem, or it can mean your host is blocking outbound requests.

Do not treat it as the only layer

Because a score can be wrong in both directions, it should not be the only thing between a bot and your gateway. A rate limit catches the fast attacker who scores well. Gateway rules catch the payment pattern. The score catches the scripts that never load a real browser.

Each layer misses something the others catch. That is the reason to have more than one.

A bot with real reCAPTCHA tokens scoring 0.9. The first eight orders are created, then the hourly limit refuses the rest.

Where Checkout Bouncer fits

Checkout Bouncer fails open if Google is down or unreachable, keeps an events log of what it checked and blocked, and pairs the reCAPTCHA v3 check with an order-rate throttle that works with any gateway. Staff bypass and an IP allowlist keep your own team and known addresses out of the score check entirely.

See which doors into your checkout are standing open