Bot Detection
Integrate CAPTCHA and bot protection into HelloJohn sign-in and sign-up flows to prevent automated abuse.
Bot Detection
HelloJohn integrates with CAPTCHA and bot detection services to protect sign-in and sign-up flows from automated abuse.
Supported providers
| Provider | Type | Privacy | Price |
|---|---|---|---|
| Cloudflare Turnstile | Smart CAPTCHA (recommended) | Privacy-preserving | Free |
| hCaptcha | Image CAPTCHA | Privacy-preserving | Free tier |
| Google reCAPTCHA v3 | Invisible scoring | Sends data to Google | Free |
| Custom | Bring your own | Your choice | Your cost |
Cloudflare Turnstile (recommended)
Turnstile is invisible to most users and never shows image challenges. It uses machine learning to distinguish humans from bots without sending user data to third parties.
Setup
- Create a Turnstile site at dash.cloudflare.com → Turnstile
- Set your domain and get the Site Key and Secret Key
CAPTCHA_PROVIDER=turnstile
CAPTCHA_SITE_KEY=0x4AAAAAAAxxxxxxxxxxxxxxxxxxxxxxxx
CAPTCHA_SECRET_KEY=0x4AAAAAAAxxxxxxxxxxxxxxxxxxxxxxxx
CAPTCHA_REQUIRED_FOR=sign_in,sign_up,magic_linkFrontend integration
Add the Turnstile widget to your auth forms:
<!-- In your <head> -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>import { useRef } from 'react';
function SignUpForm() {
const captchaToken = useRef<string | null>(null);
return (
<form onSubmit={async (e) => {
e.preventDefault();
if (!captchaToken.current) return;
await signUp({
email: e.currentTarget.email.value,
password: e.currentTarget.password.value,
captchaToken: captchaToken.current,
});
}}>
<input name="email" type="email" />
<input name="password" type="password" />
{/* Turnstile widget */}
<div
className="cf-turnstile"
data-sitekey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
data-callback="onTurnstileSuccess"
/>
<button type="submit">Sign Up</button>
</form>
);
}The HelloJohn SDK automatically forwards captchaToken to the API:
import { useSignUp } from '@hellojohn/react';
const { signUp } = useSignUp();
await signUp({
email: 'alice@example.com',
password: 'strongpassword',
captchaToken: turnstileToken, // ← pass the token here
});How HelloJohn verifies it
When HelloJohn receives a sign-up or sign-in request with a captcha_token, it calls the provider's server-side verification API:
POST https://challenges.cloudflare.com/turnstile/v0/siteverify
{
"secret": "your-secret-key",
"response": "<token-from-client>",
"remoteip": "203.0.113.42"
}If verification fails, HelloJohn returns 400 Bad Request with "code": "captcha_failed".
hCaptcha
CAPTCHA_PROVIDER=hcaptcha
CAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001
CAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000Frontend:
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<div class="h-captcha" data-sitekey="YOUR_SITE_KEY" data-callback="onCaptchaSuccess"></div>Google reCAPTCHA v3
reCAPTCHA v3 is invisible — it runs in the background and returns a score. HelloJohn rejects requests below the threshold score:
CAPTCHA_PROVIDER=recaptcha_v3
CAPTCHA_SITE_KEY=6Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CAPTCHA_SECRET_KEY=6Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CAPTCHA_RECAPTCHA_MIN_SCORE=0.5 # 0.0 (bot) to 1.0 (human)Frontend:
// Get a token for each action
const token = await grecaptcha.execute('YOUR_SITE_KEY', { action: 'signup' });
await signUp({
email: 'alice@example.com',
password: 'password',
captchaToken: token,
});When CAPTCHA is triggered
By default, CAPTCHA is optional unless triggered by security signals. Configure when CAPTCHA is required:
# Always require CAPTCHA on these flows
CAPTCHA_REQUIRED_FOR=sign_up # Always for sign-up
CAPTCHA_REQUIRED_FOR=sign_in,sign_up # Both sign-in and sign-up
CAPTCHA_REQUIRED_FOR=none # Never (disable)
# Require only after suspicious activity
CAPTCHA_TRIGGER_AFTER_FAILURES=3 # Require after 3 failed login attempts
CAPTCHA_TRIGGER_ON_NEW_IP=true # Require on first login from a new IPCustom CAPTCHA provider
If you use a different bot detection solution, implement the custom provider interface:
CAPTCHA_PROVIDER=custom
CAPTCHA_VERIFY_URL=https://your-captcha-service.com/verify
CAPTCHA_VERIFY_TOKEN_FIELD=token # Field name in the verify request
CAPTCHA_VERIFY_SUCCESS_FIELD=success # Field name in the verify responseHelloJohn will POST the token to CAPTCHA_VERIFY_URL and read success: true from the response.
Disabling CAPTCHA
CAPTCHA_PROVIDER=none
# or
CAPTCHA_REQUIRED_FOR=noneCAPTCHA can still be triggered by rate limit events even when not globally required.
Testing
Use provider-specific test keys that always return success in development:
# Turnstile test keys (always pass)
CAPTCHA_SITE_KEY=1x00000000000000000000AA
CAPTCHA_SECRET_KEY=1x0000000000000000000000000000000AA
# hCaptcha test keys (always pass)
CAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001
CAPTCHA_SECRET_KEY=0x0000000000000000000000000000000000000000In test environments, set CAPTCHA_PROVIDER=none or use test keys to avoid requiring real CAPTCHA solutions during automated testing.