Knowledge > Integrations > Bot Protection
CURRENT STRATEGY (2026-04-06): Primary: Vercel's built-in Bot Protection (Vercel Firewall) — active on all projects, configured in Vercel dashboard, no code changes needed. Backup/Supplemental: Cloudflare Turnstile — retained as a planned additional layer for forms. Not yet implemented in code (
turnstile.ts,Turnstile.tsx,verify-captcha/route.tsdo not exist yet). Decision: Founder confirmed April 2026 — use Vercel Bot Protection as primary, keep Cloudflare Turnstile as backup. Implement Turnstile on onboarding form if bot signups become an issue.
Bot Protection Strategy
Primary: Vercel Bot Protection
Vercel's built-in bot protection is active at the infrastructure level — no code changes required. It blocks known bot IPs, crawlers, and scraper traffic before requests reach the Next.js application.
Configure at: Vercel Dashboard → Project → Settings → Security → Bot Protection
All three projects (churchwiseai-web, pewsearch, sermon-illustrations) should have this enabled.
Backup: Cloudflare Turnstile (Planned)
Cloudflare Turnstile is the planned CAPTCHA replacement for public-facing forms in churchwiseai-web. It is invisible or minimally-intrusive (compact widget), privacy-preserving, and free. Not yet implemented — keep for cases where form-level bot protection is needed beyond what Vercel provides.
Account
| Field | Value |
|---|---|
| Provider | Cloudflare |
| Dashboard | cloudflare.com → Turnstile |
| Account | john@churchwiseai.com |
| Codebase | churchwiseai-web only |
How It Works
- A form renders the
<Turnstile />component (client-side widget) - Cloudflare analyzes the browser/user and issues a one-time
token - On form submit, the token is sent to the API route alongside the form data
- The API route calls
verifyTurnstile(token)which POSTs to Cloudflare's verification endpoint - If verification passes, form processing continues; otherwise it returns 403
Key Files
src/lib/turnstile.ts — Server-side verification
export async function verifyTurnstile(token: string): Promise<boolean>
- Calls
https://challenges.cloudflare.com/turnstile/v0/siteverify - Fails closed in production — if
TURNSTILE_SECRET_KEYis not set, returnsfalse - Bypasses in dev — if secret key is missing in non-production, logs a warning and returns
true(so forms work without Cloudflare config locally)
src/components/Turnstile.tsx — Client widget
import { Turnstile } from '@/components/Turnstile';
// ...
<Turnstile onSuccess={(token) => setTurnstileToken(token)} />
- Wraps
@marsidev/react-turnstile size: 'compact',theme: 'light',refreshExpired: 'auto'- Returns
nullsilently ifNEXT_PUBLIC_TURNSTILE_SITE_KEYis not set (forms still work without widget in dev)
src/app/api/auth/verify-captcha/route.ts — Standalone verification endpoint
POST /api/auth/verify-captcha — accepts { turnstileToken }, returns { ok: true } or 403. Rate-limited to 10 requests/min per IP. Used by forms that verify captcha as a separate pre-flight step.
Forms That Use Turnstile
| Form | File | How it verifies |
|---|---|---|
| Church onboarding | src/app/onboard/components/OnboardForm.tsx | Token sent with form POST |
| Contact form | src/app/contact/components/ContactForm.tsx | Token sent with form POST |
| Newsletter signup | src/components/FooterNewsletter.tsx | Token sent to /api/newsletter |
| Care subscribe | src/components/care/CareSubscribeForm.tsx | Token sent to /api/care/subscribe |
| Auth signup | src/components/auth/SignupForm.tsx | Token sent with signup request |
Environment Variables
| Variable | Where | Purpose |
|---|---|---|
NEXT_PUBLIC_TURNSTILE_SITE_KEY | Vercel (production + preview) | Public key — embedded in the client-side widget |
TURNSTILE_SECRET_KEY | Vercel (production + preview) | Server secret — used to verify tokens with Cloudflare |
Both keys come from the Cloudflare Turnstile dashboard. Each domain/subdomain can have its own site key, or you can use a wildcard site key.
Dev note: Neither key is needed for local development. The widget returns
nullandverifyTurnstile()bypasses with a warning log.
Behavior Summary
| Environment | Widget shown | Verification |
|---|---|---|
| Production (keys set) | Yes (compact, light) | Real Cloudflare check |
| Dev (no keys) | Hidden (returns null) | Always passes with console warn |
| Production (keys missing) | Hidden | Always fails (fail-closed) |
See Also
- Infrastructure Reference — env var matrix
- API Catalog —
/api/auth/verify-captcharoute