hCaptcha Examples
hCaptcha provides privacy-focused bot protection with both visible and invisible modes. Unlike reCAPTCHA, the same site key works for both visible and invisible implementations.
Visible hCaptcha
Visible hCaptcha displays a checkbox that users must interact with. The token is generated automatically when the user completes the challenge.
Key Points for hCaptcha
Configuration
- Single site key: Same key works for both visible and invisible modes
- Visible mode: Use
size: 'normal'orsize: 'compact' - Invisible mode: Use
size: 'invisible'and manual execution
Best Practices
- Always handle all three event callbacks (
onToken,onError,onExpired) - Reset widgets after successful form submission
- For invisible mode, trigger execution on form submission, not page load
- hCaptcha provides better privacy compliance compared to reCAPTCHA
Basic Implementation
import { Captcha } from '@thg-altitude/captcha';
const hcaptcha = new Captcha({
provider: 'hcaptcha',
siteKey: 'your-hcaptcha-site-key' // Same key works for both visible and invisible
});
await hcaptcha.render('captcha-container', 'contact-form', {
size: 'normal', // 'normal' | 'compact'
onToken: (captchaToken) => {
console.log('hCaptcha solved:', captchaToken);
setToken(captchaToken);
},
onError: () => {
showErrorMessage('Security verification failed. Please try again.');
},
onExpired: () => {
console.log('hCaptcha token expired');
showMessage('Security verification expired. Please complete it again.');
}
});
React Implementation
import { useEffect, useRef, useState } from 'react';
import { Captcha } from '@thg-altitude/captcha';
function ContactForm() {
const captchaRef = useRef<HTMLDivElement>(null);
const captcha = useRef<Captcha>();
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [error, setError] = useState<string>('');
useEffect(() => {
captcha.current = new Captcha({
provider: 'hcaptcha',
siteKey: 'your-hcaptcha-site-key'
});
if (captchaRef.current) {
captcha.current.render(captchaRef.current, 'contact-form', {
size: 'normal',
onToken: handleCaptchaToken,
onError: handleCaptchaError,
onExpired: handleCaptchaExpired
});
}
return () => {
captcha.current?.reset('contact-form');
};
}, []);
const handleCaptchaToken = (token: string) => {
setCaptchaToken(token);
setError('');
};
const handleCaptchaError = () => {
setCaptchaToken(null);
setError('Security verification failed. Please try again.');
};
const handleCaptchaExpired = () => {
setCaptchaToken(null);
setError('Security verification expired. Please complete it again.');
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!captchaToken) {
setError('Please complete the security verification.');
return;
}
try {
await submitForm(captchaToken);
captcha.current?.reset('contact-form');
setCaptchaToken(null);
} catch (error) {
setError('Form submission failed. Please try again.');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Email" required />
<textarea placeholder="Message" required />
{error && <div className="error-message">{error}</div>}
<div ref={captchaRef} id="captcha-container"></div>
<button type="submit" disabled={!captchaToken}>
Submit
</button>
</form>
);
}
Invisible hCaptcha
Invisible hCaptcha runs in the background and only shows a challenge when suspicious activity is detected. It uses the same site key as visible mode but requires manual execution.
Basic Implementation
import { Captcha } from '@thg-altitude/captcha';
const invisibleHcaptcha = new Captcha({
provider: 'hcaptcha',
siteKey: 'your-hcaptcha-site-key' // Same key as visible mode
});
await invisibleHcaptcha.render('container-id', 'form-protection', {
size: 'invisible',
onToken: (token) => {
// Automatically submit form when token is received
submitForm(token);
},
onError: () => {
console.error('hCaptcha verification failed');
}
});
// Execute on form submission
document.getElementById('submit-btn').onclick = async () => {
try {
await invisibleHcaptcha.execute('form-protection');
// Token will be provided via onToken callback
} catch (error) {
console.error('CAPTCHA verification failed:', error);
}
};
React Implementation
import { useEffect } from 'react';
import { Captcha } from '@thg-altitude/captcha';
function ContactForm() {
let captcha;
const captchaId = 'contact-form';
const containerId = 'contact-form-hcaptcha';
useEffect(() => {
captcha = new Captcha({
provider: 'hcaptcha',
siteKey: 'your-hcaptcha-site-key'
});
captcha.render(containerId, captchaId, {
size: 'invisible',
onToken: (captchaToken) => {
submitForm(captchaToken);
},
onError: () => {
console.error('Error rendering captcha:', captchaId);
}
});
}, []);
const submitForm = async (token) => {
try {
await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Captcha-Response': token
},
body: JSON.stringify(formData)
});
} catch (error) {
console.error('Form submission failed:', error);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!captcha) return;
try {
await captcha.execute(captchaId);
// Form submission happens in onToken callback
} catch (error) {
console.error('hCaptcha failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Name" required />
<input type="email" placeholder="Email" required />
<textarea placeholder="Message" required></textarea>
<div id={containerId}></div>
<button type="submit">Submit</button>
</form>
);
}
Vue Implementation
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" type="text" placeholder="Name" required />
<input v-model="form.email" type="email" placeholder="Email" required />
<textarea v-model="form.message" placeholder="Message" required></textarea>
<div id="captchaContainer"></div>
<button type="submit">Submit</button>
</form>
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive } from 'vue';
import { Captcha } from '@thg-altitude/captcha';
let captcha: Captcha;
const captchaId = 'contact-form';
const containerId = 'captchaContainer';
const form = reactive({
name: '',
email: '',
message: ''
});
onMounted(async () => {
captcha = new Captcha({
provider: 'hcaptcha',
siteKey: 'your-hcaptcha-site-key'
});
await captcha.render(containerId, captchaId, {
size: 'invisible',
onToken: (captchaToken) => {
submitForm(captchaToken);
},
onError: () => {
console.error('Error rendering captcha:', captchaId);
}
});
});
onUnmounted(() => {
captcha?.reset(captchaId);
});
const submitForm = async (token) => {
try {
await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Captcha-Response': token
},
body: JSON.stringify(form)
});
} catch (error) {
console.error('Form submission failed:', error);
}
};
const handleSubmit = async () => {
if (!captcha) return;
try {
await captcha.execute(captchaId);
} catch (error) {
console.error('hCaptcha failed:', error);
}
};
</script>