Cloudflare Turnstile
Cloudflare Turnstile is a privacy-friendly CAPTCHA alternative that usually verifies visitors without any puzzle. Frontmail uses it to stop bots from abusing your public key.
When a template has Require CAPTCHA (Turnstile) turned on, every public-key request for it
must include a valid Turnstile token, otherwise it fails with
captcha_required or
captcha_failed before it costs you a credit. Requests with a
private key (from your server) never need a CAPTCHA.
How it works
Section titled “How it works”| Where the form runs | Whose Turnstile keys | What you do |
|---|---|---|
| Your website | Yours – a free widget in your own Cloudflare account | Create the widget, paste both keys into Security → Bot protection (Turnstile), put the site key into your form. |
Mobile app (@frontmail/react-native) |
Frontmail’s shared mobile key | Nothing – <TurnstileWebView> loads the key itself. |
Why your own keys for websites
Section titled “Why your own keys for websites”The free Turnstile plan allows 10 hostnames per widget and 20 widgets per account; a widget that works on any hostname is only available on Cloudflare’s Enterprise plan. A single Frontmail key therefore can’t cover all our customers’ websites. Your own widget is free, takes about two minutes to set up and only works on the hostnames you list – so nobody can reuse your CAPTCHA on another site.
1. Create a Turnstile widget in Cloudflare
Section titled “1. Create a Turnstile widget in Cloudflare”- Sign in to the Cloudflare dashboard (a free account is enough – your domain doesn’t have to use Cloudflare).
- Open Turnstile and click Add widget.
- Give it a name, e.g. Contact form.
- Add all hostnames your form runs on, e.g.
example.comandwww.example.com. Addlocalhostonly if you want to test with real keys locally – for local development you can use test keys instead. - Widget mode: Managed is fine (Cloudflare shows a checkbox only when it’s unsure).
- Click Create and copy the Site Key and the Secret Key.
2. Add the keys to Frontmail
Section titled “2. Add the keys to Frontmail”- In the Frontmail dashboard open Security and find the Bot protection (Turnstile) card.
- Paste the site key and the secret key and click Save keys.
- Frontmail checks the secret with Cloudflare right away – a wrong secret is rejected with a clear message. The secret is stored encrypted and never shown again. You can change the keys at any time with Replace keys or delete them with Remove keys.
- After saving, click Turn on CAPTCHA for all templates to switch the CAPTCHA on for all existing templates in one go. Templates used as an auto-reply are skipped – they’re sent by Frontmail, not by your form.
From now on, new templates have the CAPTCHA turned on by default. Templates created before you added keys start with the CAPTCHA off – use the button above or turn it on per template in the template editor under Settings → Protection.
Without keys, the template editor won’t let you turn the CAPTCHA on and links you to the
Security page. The exception is Allow mobile apps: then you can turn it on (with a warning),
because mobile apps use Frontmail’s shared key – requests from websites to that template fail with
captcha_not_configured until you add your keys.
3. Add the widget to your form
Section titled “3. Add the widget to your form”Use your site key (it’s public – the secret key never leaves Frontmail). The template editor’s Snippets tab already contains the widget with your site key when the template requires the CAPTCHA.
With plain HTML and sendForm – render the widget inside the form. Turnstile adds a hidden
cf-turnstile-response field, which sendForm sends automatically:
<form id="contact"> <!-- your fields --> <div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div> <button>Send</button></form>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>With the framework components – pass your site key to FrontmailForm and it loads Turnstile,
renders the widget, sends the token and resets the widget for you:
- React and Svelte:
<FrontmailForm turnstileSiteKey="YOUR_TURNSTILE_SITE_KEY" …> - Vue:
<FrontmailForm turnstile-site-key="YOUR_TURNSTILE_SITE_KEY" …>
With send() – render the widget yourself and pass the token in the options:
await send('svc_01J9…', 'tpl_contact', params, { turnstileToken: token });Over the REST API, pass turnstile_token in the JSON body, or cf-turnstile-response in
multipart forms.
How Frontmail verifies the token
Section titled “How Frontmail verifies the token”Requests from a website carry an Origin (or Referer) header. Frontmail verifies their token
with your secret key and also checks that Cloudflare solved the widget on the same hostname the
request came from. A token from another hostname fails with captcha_failed
(details.reason: "hostname_mismatch") – so list every hostname of your site in the widget.
Mobile apps
Section titled “Mobile apps”@frontmail/react-native needs no site key. Its
<TurnstileWebView> fetches Frontmail’s mobile site key from
GET /v1/public-config (cached) and shows the widget as an
inline page with the URL https://mobile.frontmail.dev. Native requests have no Origin header.
Turn on Allow mobile apps – without it such requests
are refused before the CAPTCHA check (also when no allowed websites are set). Only then does
Frontmail verify them with its shared mobile secret; an organization that hasn’t allowed mobile apps
never has its CAPTCHA checked against the shared, publicly available widget.
Your own key in the app
Section titled “Your own key in the app”You can use your own widget in the app too: pass siteKey and a baseUrl whose hostname is listed
in your widget (e.g. https://example.com) to <TurnstileWebView>. The SDK then marks the request
with turnstile_key: "org" automatically and Frontmail verifies it with your secret.
Other native clients (Flutter, Swift, Kotlin…) calling the REST API
directly can do the same: render your widget in a WebView and send turnstile_key: "org" along with
turnstile_token.
Local development
Section titled “Local development”Cloudflare publishes test keys that work offline, on any hostname:
| Key | Value | Result |
|---|---|---|
| Site key | 1x00000000000000000000AA |
Always passes |
| Secret key | 1x0000000000000000000000000000000AA |
Always passes |
| Secret key | 2x0000000000000000000000000000000AA |
Always fails |
Frontmail accepts them in Security → Bot protection (Turnstile) in development environments.
In automated tests you can also send with a private key instead – private-key requests skip the check.
Tokens
Section titled “Tokens”- A token is valid for about 5 minutes and can be used once.
- After each send (successful or not), reset the widget (
turnstile.reset()) before the next attempt. The framework components do this automatically. - SDK retries of the same logical send reuse the same idempotency key. A retried request that was already accepted is answered from the first attempt before the Turnstile check, so a used token doesn’t make the retry fail.
Errors
Section titled “Errors”| Code | Status | When | Fix |
|---|---|---|---|
captcha_required |
403 | The template requires the CAPTCHA and the request has no token. | Add the widget to the form, or pass turnstileToken. |
captcha_failed |
403 | Cloudflare rejected the token – invalid, expired, already used, or solved on another hostname (hostname_mismatch). |
Reset the widget and try again; add all your hostnames to the widget. |
captcha_not_configured |
403 | A website request for a template with the CAPTCHA on, but your organization has no Turnstile keys (or, in production, only Cloudflare test keys). | Add the keys of a real widget in Security → Bot protection (Turnstile). |
Turning it off
Section titled “Turning it off”You can turn off Require CAPTCHA (Turnstile) per template in the template editor under Settings → Protection – for example for templates only sent from your server. For public forms we strongly recommend keeping it on.