Products
Captcha

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' or size: '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>