Best Practices
Follow these guidelines to ensure secure and optimal integration of gkCAPTCHA.
Security
Keep Your Secret Key Secure
- Never expose your secret key in client-side code
- Store it in environment variables
- Rotate keys if they are compromised
// Server-side only
const secretKey = process.env.GKCAPTCHA_SECRET_KEY;
// NEVER do this
const secretKey = "sk_live_xxxx"; // Exposed in client bundle!Always Verify Server-Side
Never trust client-side verification alone. Always verify the token on your server.
// On your server (Node.js). Never send your secret key to the browser.
// The form arrives URL-encoded: app.use(express.urlencoded({ extended: false }))
const response = await fetch('https://gkcaptcha.gatekeeper.sa/api/v1/token/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
siteKey: process.env.GKCAPTCHA_SITE_KEY,
secret: process.env.GKCAPTCHA_SECRET_KEY,
token: req.body['gk-captcha-token'],
}),
});
const result = await response.json();
// { success, score, timestamp, decision?, error?, reasonCode? }
// success only means the token is authentic. A "block" decision must be rejected.
if (result.success && result.decision !== 'block') {
// Passed: process the form.
} else {
// Failed or blocked: reject the request.
}Performance
Load the Widget Asynchronously
Use async and defer to prevent blocking page load.
<script src="https://gkcaptcha.gatekeeper.sa/widget/gk-captcha.js" async defer></script>Lazy Load When Possible
Only load the widget when the user reaches the form.
// Load widget when form is in viewport
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadCaptchaWidget();
observer.disconnect();
}
});
observer.observe(document.getElementById('contact-form'));Use Invisible Mode for Low-Friction UX
For trusted users or low-risk actions, use invisible mode.
<gk-captcha sitekey="pk_live_..." mode="invisible"></gk-captcha>User Experience
Provide Clear Feedback
Show loading states and error messages.
const captcha = document.querySelector('gk-captcha');
captcha.addEventListener('captcha-verified', (event) => {
// The token is also in the form's gk-captcha-token field.
console.log('Verified:', event.detail.token);
});
captcha.addEventListener('captcha-expired', () => {
// The token expired. The widget is ready to verify again.
});
captcha.addEventListener('captcha-error', (event) => {
console.warn('gkCAPTCHA error:', event.detail.code);
});Handle Token Expiration
Tokens expire after about 5 minutes. The widget fires captcha-expired, clears the form field and can verify again. At submit time, getToken() never returns an expired token.
Common Mistakes
Mistake 1: Not Handling Errors
const form = document.querySelector('#signup-form');
const captcha = document.createElement('gk-captcha');
captcha.setAttribute('sitekey', 'pk_live_...');
captcha.setAttribute('mode', 'invisible');
form.appendChild(captcha);
form.addEventListener('submit', async (event) => {
event.preventDefault();
try {
await customElements.whenDefined('gk-captcha');
await captcha.getToken(); // also fills the gk-captcha-token field
// URL-encoded, so a standard express.urlencoded() server reads req.body['gk-captcha-token'].
await fetch(form.action, { method: 'POST', body: new URLSearchParams(new FormData(form)) });
} catch (error) {
// Show an error message. Never send the form without a token.
} finally {
captcha.reset(); // each token works once
}
});Mistake 2: Reusing Tokens
Each token works once. After your server verifies it, call reset() before the next submission. Never store tokens in the browser for later use.
Mistake 3: Client-Only Validation
The captcha-verified event only informs the page. Only your server's call to /api/v1/token/verify proves the visitor passed.