Skip to content

A CAPTCHA field is the part of a web form responsible for producing evidence that the submitter is a human, not an automated script. It is not a single HTML input — it is the combination of a widget rendered into the DOM, a token that widget produces, a hidden field carrying that token in the form payload, and a server-side verification call that ratifies the token before your business logic runs.

If any of those four pieces is missing, the field offers no protection — the form simply has a decoration on it.

Anatomy of a CAPTCHA field

A modern CAPTCHA field has four moving parts:

PartLives inWhat it does
Widget containerBrowser DOMRenders the user-visible (or invisible) challenge
TokenJavaScript memorySingle-use string returned by the widget after the user passes
Hidden inputThe submitted formCarries the token to your server with the rest of the form
Verify callYour backendPOSTs the token to the CAPTCHA provider to confirm authenticity

A common bug is to render the widget but never read its token, or to read it on the client but never verify it on the server. In both cases the form looks protected and is not.

A minimal HTML example

html
<form id="signup" method="post" action="/signup">
  <input name="email" type="email" required>
  <input name="password" type="password" required>

  <!-- Widget container; the SDK injects the iframe here -->
  <div class="captchala" data-app-key="YOUR_PUBLIC_KEY"></div>

  <!-- Hidden field populated by the widget callback -->
  <input type="hidden" name="captcha_token" id="captcha_token">

  <button type="submit">Create account</button>
</form>

<script src="https://cdn.captcha-cdn.net/captchala-loader.js" defer></script>
<script>
  // The SDK exposes a callback when the user passes the challenge.
  window.captchalaOnSuccess = (token) => {
    document.getElementById('captcha_token').value = token;
  };
</script>

The hidden input is the actual "field" submitted to your server. Everything above it is plumbing whose only job is to populate that input correctly.

What the server does with the field

The token in the hidden field is meaningless to your server on its own — it is a reference, not a proof. Your server has to ask the CAPTCHA provider whether the token is legitimate, fresh, and bound to the right domain.

For CaptchaLa, that looks like this in PHP:

php
$token = $_POST['captcha_token'] ?? '';
$ip = $_SERVER['REMOTE_ADDR'] ?? '';

$ch = curl_init('https://apiv1.captcha.la/v1/validate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-App-Key: '    . getenv('CAPTCHALA_APP_KEY'),
        'X-App-Secret: ' . getenv('CAPTCHALA_APP_SECRET'),
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'pass_token' => $token,
        'client_ip'  => $ip,
    ]),
]);
$result = json_decode(curl_exec($ch), true);

if (empty($result['success'])) {
    http_response_code(400);
    exit('Verification failed');
}

If success is true, the field has done its job and the form is safe to process. If not, reject the submission and force the user to re-solve.

Where CAPTCHA fields commonly break

A few patterns we see often in support tickets:

  • Token reused across submissions. Tokens are single-use. If your front-end retries the same submit, the second call will fail. Reset the widget on every new attempt.
  • Token expired. Most providers expire tokens after 2–5 minutes. If your form has a long pre-submit step (e.g., uploading a large file), generate the token immediately before submit, not at page load.
  • Missing on dynamic forms. SPAs that swap out the form via client-side routing sometimes lose the widget. Re-render it after navigation, not just on initial mount.
  • Validation skipped on the server. This is the worst case: a token is generated, passed through, and ignored. The form is unprotected. Make the verify call mandatory in your route handler.

Where to go next

If you are wiring a CAPTCHA field into a new project, start from the Web SDK quickstart — it shows the exact HTML, the loader script, and the verify call in five minutes. If you are debugging an existing field, walk the four parts above in order: widget rendered? token generated? hidden input populated? server verify called? The break is almost always in one of those four spots.

Articles are CC BY 4.0 — feel free to quote with attribution