Cloudflare Turnstile
Add Cloudflare Turnstile to an HTML or JavaScript contact form: get keys, add the widget, save the secret and test it.
Cloudflare Turnstile checks that a visitor is a person without making them click traffic lights. In most cases it runs in the background and the visitor sees, at most, a small widget with a tick. You do not need to host your site on Cloudflare to use it.
Setup has three parts: create a widget in Cloudflare, add it to your form, then save the secret key in the SilentForms dashboard. Do them in that order. The moment CAPTCHA is on, every submission without a valid token is refused.
1. Create a widget in Cloudflare#
- Log in to the Cloudflare dashboard and open the Turnstile page.
- Select Add widget.
- Give it a name (for example "Contact form") and add the hostnames where the form runs, such as
example.com. - Choose a widget mode: Managed (Cloudflare decides whether to show a checkbox), Non-Interactive (shows a widget but never asks for a click) or Invisible (shows nothing). Managed is a good default.
- Select Create, then copy the site key and the secret key.
The site key is public and goes in your HTML. The secret key is private: you will paste it into SilentForms, and nowhere else. The widget mode is a Cloudflare setting, so you can change it later under the widget's Settings without touching your form. See Cloudflare's widget management guide if the dashboard has moved.
2. Add the widget to your form#
Paste the script and the widget <div> inside the <form>, above the submit button, and replace YOUR_SITE_KEY with your site key:
<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://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Send</button>
</form>When the check passes, the widget adds a hidden cf-turnstile-response field to the form with a token. The browser sends it with everything else; you do not have to name or handle it.
The widget must be inside the <form> element. Outside it, the token field is not part of the form and the submission arrives without it.
Publish the page and check that the widget appears and completes before you go on.
3. Turn it on in SilentForms#
- Sign in to the dashboard and open the form.
- Under Spam protection, set Provider to Cloudflare Turnstile.
- Paste the secret key (not the site key) and select Save CAPTCHA.
SilentForms checks the secret with Cloudflare before saving it. If you pasted the site key by mistake, it says so. The secret is stored encrypted and never shown again; to change it, paste a new one.
From now on, each submission's token is verified with Cloudflare before anything is emailed.
Submitting with JavaScript#
If you send the form with fetch, read the fields with new FormData(form). That picks up the hidden token field along with the visitor's input, so it ends up in the JSON body:
const form = document.querySelector('form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = Object.fromEntries(new FormData(form));
// data['cf-turnstile-response'] holds the 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();
// A token can be verified only once: get a fresh one for the next attempt.
turnstile.reset();
if (result.success) {
form.reset();
alert('Thanks, your message was sent.');
} else {
alert(result.message);
}
});Call turnstile.reset() after every attempt, successful or not. Tokens are single-use and expire after 5 minutes, so a second submission with the same token fails verification.
In React, Vue and similar frameworks, the widget's container must stay in the DOM while the page is open. If the framework re-renders the form and removes the <div>, render the widget explicitly with turnstile.render() instead; Cloudflare's client-side rendering docs show how.
Stop early submissions#
In Managed mode the check can take a moment. A visitor who presses Send immediately may submit before the token exists. Disable the button until Turnstile calls back:
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY" data-callback="turnstileDone"></div>
<button type="submit" id="send" disabled>Send</button>
<script>
function turnstileDone() {
document.getElementById('send').disabled = false;
}
</script>Test keys#
Cloudflare publishes dummy keys that work on any hostname, including localhost:
| Site key | Behaviour |
|---|---|
1x00000000000000000000AA | Always passes (visible) |
2x00000000000000000000AB | Always fails (visible) |
3x00000000000000000000FF | Forces an interactive challenge |
| Secret key | Behaviour |
|---|---|
1x0000000000000000000000000000000AA | Always passes |
2x0000000000000000000000000000000AA | Always fails |
To try the whole flow, use a second form in the dashboard with the passing test secret saved, and the passing test site key in your local page. Never leave test keys on a live form: they let every request through.
Errors#
| Message | Cause and fix |
|---|---|
| Captcha is required. | No token arrived. The widget is missing, outside the <form>, or the visitor submitted before it finished. |
| Captcha verification failed. Please try again. | The token was rejected: expired, already used, or created with a site key that does not belong to the saved secret. Reset the widget after each attempt and check both keys come from the same widget. |
| 503: Captcha could not be verified right now. | Cloudflare did not answer, or it no longer accepts the saved secret (for example after you rotated it in Cloudflare). Paste the new secret in the dashboard. |
To turn CAPTCHA off, set Provider back to Off and save. Remove the widget from your page afterwards, not before.
Next steps#
- Honeypot: keep it alongside Turnstile.
- Cloudflare Pages: host the form on Cloudflare too.
- Troubleshooting: more on CAPTCHA errors.