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
javascript
// 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.

server.js
// 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.

html
<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.

javascript
// 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.

html
<gk-captcha sitekey="pk_live_..." mode="invisible"></gk-captcha>

User Experience

Provide Clear Feedback

Show loading states and error messages.

app.js
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

app.js
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.