# Google reCAPTCHA

Require a reCAPTCHA v2 checkbox or a v3 score on your form so automated submissions are rejected before they are stored.

[Google reCAPTCHA](https://developers.google.com/recaptcha) checks each visitor with Google's risk analysis. Nisuform verifies the v2 "I'm not a robot" checkbox and score-based v3 with your own keys, on every plan. Google's free tier covers 10,000 checks a month across your whole Google Cloud organization, and it asks you to turn on billing above that.

If you don't have reCAPTCHA keys yet, [Cloudflare Turnstile](/docs/protection/turnstile) is quicker to set up and needs no Google Cloud project.

## Pick a key type

| Key type      | What visitors see                                            | Works with                                          |
| ------------- | ------------------------------------------------------------ | --------------------------------------------------- |
| Checkbox (v2) | An "I'm not a robot" checkbox, and sometimes an image puzzle | The styled embed, the generated code and plain HTML |
| Score (v3)    | Nothing. Google scores each visit from 0.0 to 1.0            | Your own JavaScript                                 |

Nisuform rejects v3 tokens that score below 0.5, Google's suggested starting threshold.

## Set it up

### Create a reCAPTCHA key

In the Google Cloud console, open **Fraud Defense**, then the **Keys** tab, and create a website key. Choose the checkbox type for the styled embed, or the score type for your own code, and add every domain your form is on. The key's **Key ID** is your site key.

To get the secret key, open the key's **Integration** tab and click **Use legacy key**, or **Integrate with a third-party service or plugin**. Keys you created in the older reCAPTCHA admin console work the same way.

### Add the keys to your form

Open the form's **Settings**, **Spam protection**. Turn on **Require a CAPTCHA**, choose **Google reCAPTCHA**, paste the **Site key** and the **Secret key**, and save.

The secret key is write only. Once saved, it is never shown again. Switching to Turnstile later clears both keys.

### Add the checkbox to your form

If you use the styled embed, copy it again from **Get code** in the builder or from the **Setup** tab. It includes the checkbox once reCAPTCHA is on.

For your own markup, load the reCAPTCHA script and place the checkbox inside the form:

```html
<script src="https://www.google.com/recaptcha/api.js" async defer></script>

<form action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <div class="g-recaptcha" data-sitekey="YOUR_RECAPTCHA_SITE_KEY"></div>
  <button type="submit">Send</button>
</form>
```

The checkbox adds a hidden `g-recaptcha-response` field to the form. Nisuform checks it with Google, and never stores it.

<Callout type="warn">
  Add the checkbox to your site before you turn reCAPTCHA on, or right after. While it's on, every submission without a valid token is rejected.
</Callout>

## reCAPTCHA v3

A v3 key has no checkbox. Load the script with your site key, ask Google for a token when the visitor submits, and send it as `g-recaptcha-response`:

```html
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_RECAPTCHA_SITE_KEY"></script>

<form id="contact" action="https://api.nisuform.com/s/YOUR_FORM_KEY" method="POST">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <input type="hidden" name="g-recaptcha-response">
  <button type="submit">Send</button>
</form>

<script>
  const form = document.getElementById('contact')
  form.addEventListener('submit', (event) => {
    event.preventDefault()
    grecaptcha.ready(async () => {
      form.elements['g-recaptcha-response'].value = await grecaptcha.execute('YOUR_RECAPTCHA_SITE_KEY', { action: 'submit' })
      form.submit()
    })
  })
</script>
```

Tokens expire after two minutes, so ask for one on submit rather than when the page loads. A visitor who scores below 0.5 gets `captcha_failed`, and v3 has no puzzle to fall back on. If real visitors are getting blocked, switch to a checkbox key or to Turnstile.

## With fetch

When you submit with JavaScript, send the token along with your fields. `FormData` picks up the checkbox's hidden field:

```js
const response = await fetch(form.action, {
  method: 'POST',
  headers: { Accept: 'application/json' },
  body: new FormData(form),
})
const result = await response.json()
if (result.error === 'captcha_failed') {
  grecaptcha.reset()
}
```

A token can only be used once. Reset the checkbox after a failed submit so the visitor can tick it again.

## When the check fails

A missing, expired, reused or invalid token, or a v3 score below 0.5, is rejected with `403` and `captcha_failed`, and the message "Captcha verification failed. Complete the captcha and try again". Nothing is stored, so the visitor can try again.

If every submission fails:

* Check that the site key in your page and the keys in your form settings belong to the same reCAPTCHA key.
* Check that your site's domain is in the key's domain list.
* Check that the checkbox, or the hidden `g-recaptcha-response` input for v3, is inside the `<form>` element.
* Check that the key type matches your code. A checkbox key needs the checkbox, and a score key needs `grecaptcha.execute`. Mixing them shows "Invalid key type" in the widget.
