# Cloudflare Turnstile

Require a Cloudflare Turnstile challenge on your form so automated submissions are rejected before they are stored.

[Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/) is a free, privacy-friendly alternative to CAPTCHAs. Most visitors never see a puzzle. You use your own Turnstile widget, and Nisuform verifies its token on every submission. It works on every plan.

## Set it up

### Create a Turnstile widget

In the Cloudflare dashboard, open **Turnstile** and add a widget. Add every hostname your form is on, like `example.com` and `www.example.com`. Copy the **site key** and the **secret key**.

### Add the keys to your form

Open the form's **Settings**, **Spam protection**. Turn on **Require Cloudflare Turnstile**, paste the **Site key** and the **Secret key**, and save.

The secret key is write only. Once saved, it is never shown again. Paste a new one to replace it.

### Add the widget 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 widget once Turnstile is on.

For your own markup, load the Turnstile script and place the widget inside the form:

```html
<script src="https://challenges.cloudflare.com/turnstile/v0/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="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div>
  <button type="submit">Send</button>
</form>
```

The widget adds a hidden `cf-turnstile-response` field to the form. Nisuform checks it, and never stores it.

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

## With fetch

When you submit with JavaScript, send the token along with your fields. `FormData` picks it up from the form:

```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') {
  turnstile.reset()
}
```

A token can only be used once. Reset the widget after a failed submit so the visitor gets a fresh token.

## When the check fails

A missing, expired, reused or invalid token 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 solve the challenge and send again.

If every submission fails:

* Check that the site key in your page and the keys in your form settings belong to the same widget.
* Check that your site's hostname is in the widget's hostname list in Cloudflare.
* Check that the widget is inside the `<form>` element, so its field is sent.
