Frequently Asked Question

GENcaptcha Client Integration Guide
Last Updated 2 hours ago

Overview

This CAPTCHA system is designed for external websites to embed a powerful yet trustworthy captcha solution with server-side validation. When enrolled, an endpoint will be provided from our CDN pool. 

From the client website point of view, the flow is:

  1. Add a container to the form.
  2. Load the hosted script from /captcha/sdk.js.
  3. Render the CAPTCHA into the container.
  4. Wait until the CAPTCHA reports that submission is allowed.
  5. Submit the form with the generated token.
  6. Verify that token from your backend using /captcha/verify.php.

The browser should never be treated as the final authority. The final decision must be made by your server after verifying the token.

Prerequisites

Before a client site can use the CAPTCHA:

  • the client domain must be enrolled - contact GEN for enrolment. 
  • the CHALLENGE must be chosen - we support multiple challenges.
  • the client site must be served from the registered domain

If the domain is not registered, the widget will refuse to operate.

The CHALLENGE value is used to choose which server-side challenge provider to load. It must be a valid provider name understood by the service, for example math for the simple test challenge or dice for the image-based dice challenge.

If the configured challenge name is missing, malformed, or does not map to a real provider, the widget returns a clear configuration error instead of failing silently.

Client-side integration

1. Add a container to your form

id="captcha-widget"

2. Load the hosted SDK

3. Render the CAPTCHA

What the SDK does

The hosted SDK in captcha/sdk.js:

  • creates an iframe pointing at the CAPTCHA widget
  • resizes that iframe to fit the challenge UI
  • stores the returned token in a hidden form field
  • tracks the current state of the widget
  • exposes helper methods so your form can ask whether submission is allowed

Available client methods

The current SDK exposes:

  • CaptchaWidget.render(config)
  • CaptchaWidget.invoke(instanceId)
  • CaptchaWidget.isSolved(instanceId)
  • CaptchaWidget.getState(instanceId)
  • CaptchaWidget.getToken(instanceId)
  • CaptchaWidget.canSubmit(instanceId)
  • CaptchaWidget.reset(instanceId)
  • CaptchaWidget.destroy(instanceId)

Recommended usage

For most forms, the main method you need is:

CaptchaWidget.canSubmit('signup-captcha')

That gives a simple true or false answer for whether the form should be allowed to submit.

Hidden token field

If you pass:

hiddenInput: 'captcha_token'

the SDK will automatically create or populate a hidden form field named captcha_token.

That means when the form submits, your backend will receive:

captcha_token=

Server-side verification

Your backend must verify the token with captcha/verify.php.

CORS

Yes, CORS needs to be considered.

What does and does not need CORS

  • Loading captcha/sdk.js with a normal does not require CORS headers.
  • Loading the iframe from captcha/widget.php does not use XHR or fetch, so normal iframe embedding is not controlled by CORS in the same way.
  • Server-to-server verification against captcha/verify.php does not rely on browser CORS at all.

Where CORS matters

CORS matters if you decide to make browser-side fetch() or XHR calls directly to CAPTCHA API endpoints from the client page.

For the current implementation, the main browser flow is:

  • host page loads captcha/sdk.js
  • SDK embeds captcha/widget.php
  • the iframe posts messages back to the parent window

That means the primary widget flow avoids most CORS complexity.

Recommended CORS policy

If browser API endpoints are later added for direct cross-origin fetch() requests, the service should:

  • return Access-Control-Allow-Origin only for registered domains
  • avoid using * for protected endpoints
  • optionally vary the response by Origin
  • support OPTIONS preflight where needed
  • only allow the methods actually required, such as POST and OPTIONS

Example policy approach:

  • if Origin matches a domain in the CAPTCHA table, echo that exact origin in Access-Control-Allow-Origin
  • set Vary: Origin
  • set Access-Control-Allow-Methods: POST, OPTIONS
  • set Access-Control-Allow-Headers: Content-Type

Recommended position for this CAPTCHA

  • keep browser interaction inside the iframe and SDK
  • keep trust decisions on the server side
  • use server-to-server verification from the client site's backend
  • only add CORS to browser API endpoints if direct cross-origin AJAX becomes necessary

So, from a client integration point of view, the normal embed flow should work without the client needing to do anything special for CORS.

Example PHP verification request

 $token,
    'domain' => 'www.example.com'
]);

$ch = curl_init('https://endpoint/captcha/verify.php');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Content-Length: ' . strlen($payload),
    ],
    CURLOPT_TIMEOUT => 15,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$result = json_decode($response, true);

if ($httpCode === 200 && !empty($result['valid'])) {
    // allow the protected action
} else {
    // reject the submission
}

Verify request body

Send JSON like:

{
  "token": "",
  "domain": "www.example.com"
}

Successful verify response

{
  "status": "success",
  "valid": true,
  "domain": "www.example.com",
  "captid": 123,
  "userid": 0,
  "created": "2026-03-28 15:00:00",
  "expires": "2026-03-28 15:05:00",
  "result": "Y",
  "caller_ip": "203.0.113.10"
}

Failed verify response

{
  "status": "fail",
  "valid": false,
  "reason": "token_not_valid"
}

Important client rules

1. Do not trust only the browser result

Even if the widget appears solved, always verify the token from your backend.

2. Keep the submit button disabled until solved

Use CaptchaWidget.canSubmit() or CaptchaWidget.isSolved() to control submit availability.

3. Expect short-lived tokens

Tokens are stored in the CAPTCHA_TOKENS  store expire quickly.

If a token is too old, verification will fail and the user should solve a fresh CAPTCHA.

4. Expect one-time-use verification

Once a valid token is verified, it is consumed and cannot be reused.

Lockouts and failures

Current behaviour:

  • the default challenge policy allows up to 5 wrong answers
  • after 5 failures, the CAPTCHA locks
  • the lock is stored in the session
  • refreshing the page does not clear that lock for the current session

Client sites should treat a locked or expired challenge as a failed submission state and instruct the user to start a new browser session if required.

Dice challenge

The recommended next visual provider is dice.

How it works

  • the widget shows an image containing two rendered dice
  • each die value is represented by dots
  • the dot placement is intentionally not perfectly regular, so the pips do not always line up like a textbook die face
  • the die faces also contain extra line work or surface markings to make simple automated recognition harder
  • the background is randomised for each challenge
  • the user enters the total shown across both dice

Why use it

  • it is still simple for a person to understand quickly
  • it keeps the answer format easy to validate
  • it is harder to treat as a trivial standard dice-recognition problem
  • it fits the existing iframe and token model without any client integration changes

Client integration impact

No special client-side code is required beyond choosing the challenge name configured for the domain.

From the embedding page point of view, the flow stays the same:

  • render the widget
  • wait for CaptchaWidget.canSubmit() to return true
  • submit the returned token
  • verify it on the backend with captcha/verify.php

Example challenge selection

If your registered domain is configured to use the dice provider, your render call can use:

window.CaptchaWidget.render({
  container: '#captcha-widget',
  instanceId: 'signup-captcha',
  challenge: 'dice',
  action: 'signup',
  hiddenInput: 'captcha_token',
  hidden: false
});

The final trust rule does not change: even after the dice challenge appears solved in the browser, your backend must still verify the token.

Form-age protections

The example test form in test/captchatest.php also demonstrates extra form protection using session timing:

  • reject submissions that happen too quickly
  • reject submissions that happen too long after form load
  • reject submissions where the original form session is missing

These checks are not part of the CAPTCHA token itself, but they are recommended for sensitive forms.

Example end-to-end client pattern

Browser

  • render CAPTCHA
  • disable submit until solved
  • submit form with captcha_token

Backend

  • receive posted form data
  • read captcha_token
  • call captcha/verify.php
  • allow or reject based on the verify response
This website relies on temporary cookies to function, but no personal data is ever stored in the cookies.
OK
Powered by GEN UK CLEAN GREEN ENERGY

Loading ...