Turnstile foundations

Separate site and secret keys

Publish the site key in browser markup while keeping the secret key only in the server environment.

When you create a Turnstile widget in the Cloudflare dashboard, you get two keys. They have very different jobs.

The site key identifies the widget. It is safe to send to browsers, and it sits in your HTML where anyone can read it:

<div class="cf-turnstile" data-sitekey="0x4AAAAAAABkMYinukE8nzKd"></div>

The secret key authenticates your Siteverify requests. It must live in a server secret store and nowhere else. On Workers or Pages, that means:

npx wrangler secret put TURNSTILE_SECRET

The word “key” hides the difference, so I keep the rule mechanical. The site key ships to the client. The secret key never does. A leaked site key is harmless. A leaked secret key lets anyone forge “this is a human” verdicts against your backend.

One widget per environment

Use separate widgets for development, staging, and production. Restrict production hostnames in the widget settings, and name widgets by purpose. Something like contact-form-prod and signup-staging tells you at a glance what each one is for.

For local work, use Cloudflare’s documented test keys instead of a real widget. Your development traffic then never touches production analytics. We look at those keys in a later lesson.

Verify nothing leaked

Frameworks make leaking easy. In Astro and Vite projects, any environment variable prefixed with PUBLIC_ gets inlined into the client bundle. Name the secret TURNSTILE_SECRET, not PUBLIC_ANYTHING, and read it only in server code.

After a build, search the output for the first characters of your real secret:

npm run build
grep -r "0x4AAAAAAABkMY_your_real_secret" dist/ && echo "LEAKED" || echo "clean"

Then do the same on the deployed form. Open devtools, search the page source and the network responses for the secret’s first characters. It should appear nowhere.

If a secret does show up in source, logs, or client JavaScript, rotate it and check every environment that used it. Rotation is cheap. An attacker quietly bypassing your bot protection for months is not.

Lesson completed