Google reCAPTCHA

Add Google reCAPTCHA v2 (checkbox) or v3 (invisible, score-based) to a contact form, with the minimum score explained.

Google reCAPTCHA comes in two versions that SilentForms supports, and they behave very differently:

  • v2 checkbox shows the familiar "I'm not a robot" box. Most visitors pass with one click; suspicious ones get a picture challenge.
  • v3 is invisible. It never shows a challenge; instead Google gives each submission a score from 0.0 (very likely a bot) to 1.0 (very likely a person), and SilentForms refuses submissions below the minimum score you set.

Pick one per form, and make sure the dashboard setting matches the key type. A v2 key under the v3 setting fails every submission, because v2 tokens carry no score; a v3 key under the v2 setting passes but ignores the score.

As with every provider, add the widget to your page first and turn CAPTCHA on in the dashboard last. From the moment it is on, submissions without a valid token are refused.

1. Create keys#

  1. Open the reCAPTCHA admin console and sign in with a Google account.
  2. Enter a label, then choose the type: Score based (v3) for v3, or Challenge (v2) with the "I'm not a robot" checkbox for v2.
  3. Add your domains, for example example.com. localhost is not allowed by default; Google suggests a separate key for development with localhost added.
  4. Accept the terms and submit.
  5. Copy the site key (for your HTML) and the secret key (for SilentForms).

reCAPTCHA keys now live in a Google Cloud project, and Google's screens have changed more than once. If you only see the Cloud console, open your key's details and look for the legacy secret key under its Integration tab ("Use Legacy Key"): that is the secret SilentForms needs. Google's key creation guide has the current steps.

v2 checkbox#

Paste the script and widget inside the <form>, above the submit button:

<form action="https://silentforms.com/api/submit" method="POST">
  <input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY_HERE">

  <label for="name">Name</label>
  <input id="name" type="text" name="name" required>

  <label for="email">Email</label>
  <input id="email" type="email" name="email" required>

  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>

  <input type="checkbox" name="botcheck" style="display: none;" tabindex="-1" autocomplete="off">

  <script src="https://www.google.com/recaptcha/api.js" async defer></script>
  <div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>

  <button type="submit">Send</button>
</form>

When the visitor ticks the box, the widget fills a g-recaptcha-response field in the form, which is submitted with everything else.

With fetch, new FormData(form) includes that field. Call grecaptcha.reset() after each attempt, because a token can only be verified once:

const data = Object.fromEntries(new FormData(form));
if (!data['g-recaptcha-response']) {
  alert('Please tick "I\'m not a robot".');
  return;
}
const response = await fetch('https://silentforms.com/api/submit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
  body: JSON.stringify(data),
});
grecaptcha.reset();

v3 invisible#

v3 has no widget to tick, so a small script asks Google for a token when the visitor submits, puts it in a hidden field and then sends the form. Paste this inside the <form>:

<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response">
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITE_KEY"></script>
<script>
  document.getElementById('g-recaptcha-response').form.addEventListener('submit', function (event) {
    event.preventDefault();
    var form = this;
    grecaptcha.ready(function () {
      grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' }).then(function (token) {
        document.getElementById('g-recaptcha-response').value = token;
        form.submit();
      });
    });
  });
</script>

Replace both occurrences of YOUR_SITE_KEY. Why the token is fetched on submit rather than on page load: Google's tokens expire after two minutes, so one fetched when the page opened would often be stale by the time a visitor finishes typing.

form.submit() sends the form without firing the submit event again, so there is no loop. The browser's required checks still run first, because the submit event only fires after they pass.

If you submit with fetch, get the token inside your handler and add it to the body:

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  await new Promise((resolve) => grecaptcha.ready(resolve));
  const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'submit' });

  const data = Object.fromEntries(new FormData(form));
  data['g-recaptcha-response'] = token;

  const response = await fetch('https://silentforms.com/api/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify(data),
  });
  const result = await response.json();
  alert(result.success ? 'Thanks, your message was sent.' : result.message);
});

v3 shows a small reCAPTCHA badge in the corner of the page. That is expected.

2. Turn it on in SilentForms#

  1. Sign in to the dashboard and open the form.
  2. Under Spam protection, set Provider to Google reCAPTCHA v2 or Google reCAPTCHA v3, matching your key.
  3. Paste the secret key and select Save CAPTCHA.

The secret is checked with Google before it is saved, then stored encrypted and never shown again.

Choosing a minimum score (v3)#

The Minimum score setting only applies to v3. It defaults to 0.5, Google's suggested starting point. A submission scoring below it is refused with "Captcha verification failed. Please try again."

  • If real visitors report that the form refuses them, lower it to 0.3.
  • If spam still gets through, raise it to 0.7.

Scores are less reliable on a brand-new key or a low-traffic site, because Google has seen little traffic for it. Google's admin console shows the score distribution for your key after a while, which is the best guide for adjusting the threshold. Keep the honeypot on the form, since it catches simple bots regardless of score.

Test keys#

For v2, Google publishes test keys that work on any domain. The widget shows a warning and every verification passes:

Value
Site key6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
Secret key6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe

Save the test secret on a separate test form, never the live one. There are no test keys for v3: create a second v3 key for development with localhost in its domain list.

Errors#

MessageCause and fix
Captcha is required.No g-recaptcha-response arrived. For v2, the box was not ticked; for v3, the script did not run or ran outside the <form>.
Captcha verification failed. Please try again.The token expired or was reused, the site key and secret belong to different keys, a v2 key is used with the v3 setting, or (v3) the score was below the minimum.
503: Captcha could not be verified right now.Google did not answer, or no longer accepts the saved secret. Save the current secret again.

Next steps#