/password/login, or use up SMS quotas through /phone/send-code. Pylon’s built-in CAPTCHA gate protects those endpoints. Set one env var pair, and Pylon blocks unauthenticated bots before they reach the rate limiter.
Three providers are built in:
- hCaptcha — independent, privacy-focused, free up to ~1M/month
- Cloudflare Turnstile — invisible challenge, free for any volume
- Google reCAPTCHA — v2 + v3 token shapes
Gated endpoints
WhenPYLON_CAPTCHA_PROVIDER + PYLON_CAPTCHA_SECRET are both set, these endpoints require a captchaToken in the request body:
When the env vars are unset, Pylon skips the gate. Existing apps keep working unchanged.
Configuration
turnstileandcloudflareboth select Turnstile.recaptchaandgoogleboth select reCAPTCHA.
siteverify endpoint with the supplied token and the request’s peer IP:
- hCaptcha:
https://api.hcaptcha.com/siteverify - Turnstile:
https://challenges.cloudflare.com/turnstile/v0/siteverify - reCAPTCHA:
https://www.google.com/recaptcha/api/siteverify
PYLON_CAPTCHA_SECRET. Get both from the provider’s dashboard.
Client integration
cf-turnstile-response; for reCAPTCHA it’s g-recaptcha-response. All three map to a single captchaToken field in the request to Pylon.
Verify behavior
Missing or invalid token returns400 CAPTCHA_FAILED:
missing-input-response, invalid-input-response) server-side with tracing::warn!. Operators can debug from logs. The client does not learn which check failed.
The gate runs before the rate limiter and before any DB or email work. Bots that fail the challenge do not consume rate-limit budget or trigger later actions.
Where to go next
- Magic codes — primary gated endpoint
- Password — register flow uses the same gate
- Phone / SMS —
/phone/send-codeis gated identically