Skip to content

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.

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.

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”
  1. Sign in to the Cloudflare dashboard (a free account is enough – your domain doesn’t have to use Cloudflare).
  2. Open Turnstile and click Add widget.
  3. Give it a name, e.g. Contact form.
  4. Add all hostnames your form runs on, e.g. example.com and www.example.com. Add localhost only if you want to test with real keys locally – for local development you can use test keys instead.
  5. Widget mode: Managed is fine (Cloudflare shows a checkbox only when it’s unsure).
  6. Click Create and copy the Site Key and the Secret Key.
  1. In the Frontmail dashboard open Security and find the Bot protection (Turnstile) card.
  2. Paste the site key and the secret key and click Save keys.
  3. 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.
  4. 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.

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.

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.

@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.

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.

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.

  • 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.
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).

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.