Set Up Cloudflare Turnstile for Your Website

What you need before you start

Cloudflare Turnstile tests if a form submission comes from a real visitor. Usually, the visitor does not have to solve a puzzle. To use Turnstile on your own domain, you need two keys from Cloudflare. The sitekey is public and goes in the HTML of your page. The secret key is private and stays on the server that receives the form.

You also need a Cloudflare account. Your site does not have to use the DNS or the proxy of Cloudflare. Turnstile works on any website, on any host.

You need your own keys when your site on your own domain accepts submissions from visitors. Examples are contact, feedback or signup forms, comments and all other features that write data. A read-only site does not need Turnstile.

Each pair of keys belongs to a widget, and each widget has a list of hostnames. A widget works only on the domains that you add to it. For this reason, each custom domain needs a widget that lists it.

Step 1: Open Turnstile in the Cloudflare dashboard

  1. Sign in to the Cloudflare dashboard.
  2. Open Turnstile from the account sidebar.

Step 2: Add a widget

  1. Select Add widget.
  2. Enter a Widget name that you will recognize later, for example Contact form - example.com.

One widget can serve several pages and forms on the same domains. If you want different settings, create separate widgets, for example one for each site.

Step 3: Add your custom domain as a hostname

Under Hostname management, add the domain of the form (hostname management). Follow these rules:

  • Enter only the bare domain: example.com, not https://example.com, example.com/contact or example.com:8080. Cloudflare rejects protocols, paths and ports.
  • A root domain covers its subdomains. When you add example.com, it also covers www.example.com and app.example.com.
  • The form can also show on the default address of your host, such as <project>.pages.dev or <site>.netlify.app. In that case, add that hostname too.
  • Free accounts can add up to 10 hostnames for each widget.

For local development, do not add localhost to your production widget. Use the Cloudflare test keys instead (see below).

Step 4: Choose the widget mode

Cloudflare offers three modes (widget types):

Mode What the visitor sees
Managed (recommended) Usually nothing to do. A checkbox appears only when Cloudflare wants more verification
Non-interactive A visible widget with a loading spinner. It never asks the visitor to act
Invisible Nothing at all. Your privacy policy must refer to Cloudflare’s Turnstile Privacy Addendum

If you have no reason to use a different mode, select Managed. Keep Pre-clearance off. Turn it on only if you run a single-page app and know that you need it.

Step 5: Create the widget and copy the keys

Select Create. Cloudflare shows the two keys (dashboard widget management):

Key What it is Where it goes
Sitekey Public identifier for the widget In the HTML of the page with the form
Secret key Private key to validate tokens In the environment variables or secrets of your server. Never put it in HTML, browser JavaScript or a public repository

Step 6: Add the widget to your form

Load the Cloudflare script. Then put the widget inside the form (client-side rendering):

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form action="/api/contact" method="POST">
  <label>Email <input type="email" name="email" required></label>
  <label>Message <textarea name="message" required></textarea></label>
  <div class="cf-turnstile" data-sitekey="YOUR-SITEKEY"></div>
  <button type="submit">Send</button>
</form>

When the visitor passes the challenge, Turnstile adds a hidden field named cf-turnstile-response to the form. The field holds a token (a short-lived proof) that your server must validate.

Load api.js from that exact URL. Do not proxy or cache the file yourself. Cloudflare warns that a proxied or cached copy breaks Turnstile when Cloudflare updates the file.

Step 7: Validate every token on the server

The widget alone is not enough. Anyone can send a request directly to your form endpoint and skip the page. Server-side validation is required. Your server sends the token and your secret key to the Siteverify endpoint of Cloudflare. The server accepts the form only when the answer is success: true.

Know these rules:

  • The Siteverify address is https://challenges.cloudflare.com/turnstile/v0/siteverify. It accepts a POST with secret and response (the token), and an optional remoteip.
  • A token expires 300 seconds after Turnstile creates it.
  • You can validate a token only one time. A second validation fails with timeout-or-duplicate.
  • The response includes the hostname where Turnstile issued the token. You can compare it with your domain.

This complete example is for a site on Cloudflare Pages. It is a Pages Function in functions/api/contact.js. It handles POST /api/contact from the form above:

export async function onRequestPost({ request, env }) {
  const form = await request.formData();
  const token = form.get("cf-turnstile-response") || "";

  const check = new FormData();
  check.append("secret", env.TURNSTILE_SECRET_KEY);
  check.append("response", token);
  const ip = request.headers.get("CF-Connecting-IP");
  if (ip) check.append("remoteip", ip);

  const result = await fetch(
    "https://challenges.cloudflare.com/turnstile/v0/siteverify",
    { method: "POST", body: check }
  ).then((r) => r.json());

  if (!result.success) {
    return new Response("Verification failed. Please reload and try again.", { status: 403 });
  }

  // The visitor passed: send the email, save the message, and so on.
  return new Response("Thanks, we received your message.");
}

To store the secret, open Workers & Pages. Select the project. Go to Settings > Variables and Secrets > Add. Name the variable TURNSTILE_SECRET_KEY and paste the secret. Select Encrypt. Then save (Pages bindings).

Drag-and-drop uploads in the dashboard do not compile a functions folder. For this reason, deploy with Wrangler or from Git (Direct Upload).

On other hosts, the logic is the same in any server language. Read cf-turnstile-response. Post it to Siteverify with your secret. If success is not true, reject the request. Netlify Functions, Vercel Functions and your own backend all work.

Test with Cloudflare’s dummy keys

Cloudflare publishes test keys that work on any domain, including localhost:

Test sitekey Behavior
1x00000000000000000000AA Always passes (visible widget)
2x00000000000000000000AB Always fails (visible widget)
3x00000000000000000000FF Forces an interactive challenge
Test secret key Behavior
1x0000000000000000000000000000000AA Always passes validation
2x0000000000000000000000000000000AA Always fails validation
3x0000000000000000000000000000000AA Returns timeout-or-duplicate

Test sitekeys produce the dummy token XXXX.DUMMY.TOKEN.XXXX. Only test secret keys accept this token. Use test keys in development and real keys in production. Set the keys through environment variables. Do not ship the test keys.

Troubleshooting

  • If the widget does not appear on your domain, or shows an error, the domain is probably missing from the hostname list (Step 3). Compare the list with the exact domain in the address bar of the browser. Include any subdomain that the root entry does not cover.
  • Sometimes the widget works on the default address of the host but not on the custom domain, or the reverse. In that case, add both hostnames to the widget.
  • If Siteverify returns invalid-input-secret, the secret on the server is wrong or empty. Make sure that the variable name and the value are correct.
  • If Siteverify returns missing-input-response or invalid-input-response, the token is missing or malformed. Make sure that the widget is inside the <form> element. Also make sure that your server reads the cf-turnstile-response field without changes.
  • If Siteverify returns timeout-or-duplicate, the token was older than five minutes or a server already validated it. After a failed submission, reset the widget, so that the visitor gets a new token.
  • If the secret key leaked, open the widget’s Settings. Then use Rotate Secret Key. The old and the new secret both work for about two hours while you update your server (rotate the secret key).

Turnstile is a bot check, not access control

Turnstile tells you that a submission probably came from a person. It does not identify an approved client, and it does not keep a private page private. If a site is only for certain people, also use the access control of your host. Our guides cover Cloudflare Pages with Cloudflare Access and Netlify password protection.

Free email addresses
for your AI agents

Each agent gets an address like myagent.x7k2@revdokumail.com. It can receive emails, send replies and store files.

  • 3 free mailboxes, each with its own address
  • 3,000 incoming and 500 sent emails a month
  • 3 GB of storage for attachments and files
  • Virus, malware, spam and prompt-injection filter
  • Read and reply through API, MCP or CLI
Create free mailbox

You do not need a card.

Frequently Asked Questions

Does Turnstile replace rate limiting?

No. Turnstile filters automated submissions, but a real person can still submit a form many times. Keep server-side validation. If abuse is a concern, also add a rate limit on the form endpoint. A rate limit sets a maximum number of requests in a time period.

Share:
Markdown version

Related Articles

↧
Loading PDF…