✎ ShareNotes
2026-11-12 · Engineering · By the ShareNotes team

A reCAPTCHA v3 score is a hint, not a verdict

reCAPTCHA v3 never shows a puzzle. It quietly gives each visitor a score, and it is up to the site to decide what that score means. ShareNotes uses it to choose how much to allow, never to turn someone away. Here is how that works and why.

ShareNotes lets anyone save a note without an account. That makes the save button worth protecting from scripts that would fill the database with spam, and the usual tool for that is reCAPTCHA. Version 3 is the invisible one: no traffic lights to click. The page asks Google for a token, the server asks Google what it thinks of that token, and the answer comes back as a score from 0.0 to 1.0.

The common way to use it is a threshold: 0.5 or above is a person, below is a bot, and below gets a refusal. It is simple, and it is the part we think deserves a second look.

What a v3 score actually tells you

The score is Google's guess about how human a visit looked, based on signals it can see in that browser. Many perfectly ordinary visits give it very little to go on: a fresh browser profile, strict privacy settings, a browser inside a chat or email app, a corporate network, a first visit with no history. Those visits tend to score lower. The person behind them is just as real.

So a low score doesn't mean "bot". It means "Google isn't sure". A hard refusal treats "not sure" as "no", and the people it stops are often people who simply care about their privacy.

Three ways a real person ends up without a good score

  • A low score. The token is valid and Google answered, but it isn't confident.
  • No token at all. The reCAPTCHA script never loaded: an ad or tracker blocker, a strict browser, a slow connection that gave up. The save request arrives with nothing to check.
  • Google can't be reached. The check itself fails because of a network problem or an outage between our server and Google. That says nothing at all about the visitor.

A scripted spammer can produce all three as well. That is the point: none of the three can tell a person from a script. A rule that refuses all three refuses people, and a script that simply leaves the token out gets the same treatment as a person whose blocker removed it.

Fail soft: a smaller allowance, not a refusal

So on ShareNotes the score picks how much to allow, not whether:

  • A good score gets the normal limit: up to 10 new notes an hour from one connection.
  • A low score, no token, or Google unreachable still saves the note, under a much smaller allowance: 3 new notes an hour from that connection. A person saving a note or two never notices. A script trying to post hundreds runs into the limit at the fourth.
  • A token Google rejects outright (forged, expired or reused) is still refused. That isn't uncertainty. It is a request that claimed to pass a check it didn't pass.

In Express the shape is small. The check stops for a rejected token, and otherwise only marks the request:

async function checkRecaptcha(req, res, next) {
  const token = req.body.recaptchaToken;
  if (!token) { res.locals.unverified = 'no_token'; return next(); }

  const result = await verifyRecaptcha(token);
  if (result.lowScore)         res.locals.unverified = 'low_score';
  else if (result.unavailable) res.locals.unverified = 'unavailable';
  else if (!result.success)    return res.status(403).json({ error: 'verification failed' });
  next();
}

// Applies only to requests the check couldn't vouch for.
const unverifiedLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 3,
  skip: (req, res) => !res.locals.unverified,
});

router.post('/notes', generalLimiter, checkRecaptcha, unverifiedLimiter, createNote);

Two details matter. The smaller limit sits after the general one, so a good score never buys more than the normal 10. And a missing token is treated the same as a low score, not as a pass. Otherwise the easiest way around the check would be to skip it.

The public API is a separate case. Scripts and command-line tools can't run reCAPTCHA at all, so the API has no reCAPTCHA and its own limit instead: 20 notes a day from one address.

Counting what the allowance lets through

A softer rule is only safe if you can see what it lets in. Every save the check couldn't vouch for is counted by kind: low score, no token, or Google unreachable, plus how often the smaller allowance ran out. These are counts of requests per day, nothing more. No addresses are stored with them, and they never involve what a note says. The admin view shows them next to ordinary saves, so a rise in any one kind stands out.

Those counts are also how we'd notice the scores themselves drifting. If a browser update suddenly pushed many real visits below the line, it would show up as a jump in low-score saves, not as a quiet wave of people who gave up.

When we'd tighten it again

An allowance is a dial, not a switch, which makes it easy to turn. The signals we watch are:

  • The share of saves the check couldn't vouch for rising sharply without a matching rise in ordinary use.
  • The smaller allowance running out often from the same few connections, which looks like automation, not people.
  • Notes being reported through the suggestion box or a removal request.

If those move, the first step is lowering the smaller allowance, not bringing back refusals. A hard "no" for an uncertain score is the last resort, because the people it turns away are the ones you never hear from.

The short version for anyone running reCAPTCHA v3: treat the score as one input to a rate limit, count what your fallback lets through, and keep outright refusals for tokens that are actually invalid.

Frequently asked

Why does reCAPTCHA v3 give real people a low score?

The score is Google's guess based on signals it can see in the browser. Fresh browser profiles, strict privacy settings, in-app browsers, shared networks and first visits give it little to go on, so they tend to score lower. A low score means Google isn't sure, not that the visitor is a bot.

What should a site do when the reCAPTCHA v3 score is low?

Use it to choose a smaller allowance rather than to refuse. ShareNotes still saves the note, but limits that connection to 3 new notes an hour instead of 10. A person never notices; a script hits the limit almost at once. Tokens Google rejects outright are still refused.

What if the reCAPTCHA script is blocked or Google is down?

Treat a missing token or an unreachable Google like a low score: allowed under the smaller limit, never as a pass and never as an outright refusal. Treating a missing token as a pass would make skipping the check the easiest way around it.

Does ShareNotes use reCAPTCHA on its API?

No. Scripts and command-line tools can't run reCAPTCHA, so the public API has its own limit instead: 20 notes a day from one address.

No sign-up · Free forever Save a note: no account, no puzzle →