Found this while auditing my own docs. The submission endpoint accepts cf-turnstile-response tokens, but verifyCaptcha in apps/web/src/app/api/v1/f/[slug]/route.ts only knows two verify endpoints: hCaptcha (when the secret starts with 0x) and Google reCAPTCHA (everything else). There's no Cloudflare siteverify call at all.
It's worse than a missing feature: Turnstile secrets also start with 0x, so a Turnstile secret gets sent to hCaptcha's endpoint and verification always fails. Anyone who configured Turnstile has a form that rejects every submission with a captcha error.
Why it's not a one-liner: the 0x prefix heuristic can't tell hCaptcha and Turnstile apart, so fixing this properly means an explicit provider choice:
- add a
captchaProvider field (recaptcha | hcaptcha | turnstile) to the Form model + settings UI
- branch
verifyCaptcha to https://challenges.cloudflare.com/turnstile/v0/siteverify for Turnstile
- keep the current heuristic as a fallback for existing forms with no provider set
Until this ships, treat Turnstile as unsupported.
Found this while auditing my own docs. The submission endpoint accepts
cf-turnstile-responsetokens, butverifyCaptchainapps/web/src/app/api/v1/f/[slug]/route.tsonly knows two verify endpoints: hCaptcha (when the secret starts with0x) and Google reCAPTCHA (everything else). There's no Cloudflare siteverify call at all.It's worse than a missing feature: Turnstile secrets also start with
0x, so a Turnstile secret gets sent to hCaptcha's endpoint and verification always fails. Anyone who configured Turnstile has a form that rejects every submission with a captcha error.Why it's not a one-liner: the
0xprefix heuristic can't tell hCaptcha and Turnstile apart, so fixing this properly means an explicit provider choice:captchaProviderfield (recaptcha | hcaptcha | turnstile) to the Form model + settings UIverifyCaptchatohttps://challenges.cloudflare.com/turnstile/v0/siteverifyfor TurnstileUntil this ships, treat Turnstile as unsupported.