Try PHP CAPTCHA: Working Code for Every Modern Option

The Securimage live demo is permanently offline. Securimage was abandoned in 2018, officially marked unmaintained in 2023, and its author recommends switching to hCaptcha. The library has known XSS vulnerabilities (CVE-2017-14077) that will never be patched.

This page replaces that demo with something more useful: working PHP code for every modern CAPTCHA option, so you can try each one and pick what fits your project.

Running These Examples Locally

Copy any example below into a directory, install dependencies with Composer where noted, and start PHP's built-in server:

php -S localhost:8000

Open http://localhost:8000 in your browser. Each example uses test keys where available so you can try it without signing up for anything.

Quick Comparison: Your Options in 2026

Solution Type Free Tier Self-Hosted Best For
Cloudflare Turnstile Invisible challenge 1M/month No Most PHP forms — nearly invisible, generous free tier
hCaptcha Image challenge 100K/month Enterprise only Sites needing visible proof-of-human or China access
ALTCHA Proof-of-work Unlimited (self-hosted) Yes (MIT license) GDPR-strict sites, no third-party dependency
Gregwar/Captcha Image text Unlimited (self-hosted) Yes (MIT license) Legacy replacement for Securimage, offline environments
reCAPTCHA v3 Invisible score 10K/month No Only if you're already locked into Google's ecosystem

Start with Cloudflare Turnstile unless you have a specific reason not to. It's free, invisible, and backed by Cloudflare's network intelligence. Read our full CAPTCHA alternatives comparison for the detailed breakdown.

Migrating from Securimage

If you're running Securimage in an existing project, here's the migration reality:

Option 1: Cloudflare Turnstile (Recommended Default)

Turnstile is invisible to most users — no puzzles, no checkboxes. It verifies humans using browser signals and Cloudflare's network data, then returns a token your server validates.

Frontend

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form method="POST" action="/submit.php">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>

  <!-- Turnstile widget — renders as a small inline element -->
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>

  <button type="submit">Send</button>
</form>

Server-Side Verification (PHP 8.0+)

<?php
// Cloudflare Turnstile verification
function verifyTurnstile(string $token, string $secret): bool
{
    $ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => http_build_query([
            'secret'   => $secret,
            'response' => $token,
            'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
        ]),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    if ($response === false) {
        return false;
    }

    $data = json_decode($response, true);
    return $data['success'] ?? false;
}

// Usage
$secret = getenv('TURNSTILE_SECRET') ?: '';
$valid  = verifyTurnstile($_POST['cf-turnstile-response'] ?? '', $secret);

if (!$valid) {
    $errors[] = 'Verification failed. Please try again.';
}

Test keys for local development: site key 1x00000000000000000000AA (always passes), secret key 1x0000000000000000000000000000000000000000. Full setup details in our Turnstile PHP integration guide.

Option 2: hCaptcha

hCaptcha shows image challenges to users — "select all images with motorcycles." It collects less data than reCAPTCHA but more than Turnstile or self-hosted options. The free tier (100K/month) always shows visible challenges; invisible mode requires the $99/month Pro plan.

Frontend

<script src="https://js.hcaptcha.com/1/api.js" async defer></script>

<form method="POST" action="/submit.php">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>

  <div class="h-captcha" data-sitekey="YOUR_SITE_KEY"></div>

  <button type="submit">Send</button>
</form>

Server-Side Verification (PHP 8.0+)

<?php
// hCaptcha verification — same pattern, different endpoint
function verifyHcaptcha(string $token, string $secret): bool
{
    $ch = curl_init('https://api.hcaptcha.com/siteverify');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => http_build_query([
            'secret'   => $secret,
            'response' => $token,
            'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
        ]),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    if ($response === false) {
        return false;
    }

    $data = json_decode($response, true);
    return $data['success'] ?? false;
}

// Usage
$secret = getenv('HCAPTCHA_SECRET') ?: '';
$valid  = verifyHcaptcha($_POST['h-captcha-response'] ?? '', $secret);

Test keys: site key 10000000-ffff-ffff-ffff-000000000001, secret key 0x0000000000000000000000000000000000000000. See our hCaptcha review for the full analysis of pricing, privacy, and limitations.

Option 3: ALTCHA (Self-Hosted Proof-of-Work)

ALTCHA takes a completely different approach: instead of asking users to identify images, it makes their browser solve a computational puzzle. No third-party servers, no cookies, no data collection. The user sees nothing — the puzzle solves in the background.

This is the closest modern replacement for Securimage's philosophy of keeping everything on your server.

Install

composer require altcha-org/altcha

Frontend

<!-- ALTCHA web component -->
<script src="https://cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js" defer></script>

<form method="POST" action="/submit.php">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>

  <altcha-widget
    challengeurl="/altcha-challenge.php">
  </altcha-widget>

  <button type="submit">Send</button>
</form>

Challenge Endpoint (PHP 8.0+)

<?php
// altcha-challenge.php — generates a proof-of-work challenge
require_once __DIR__ . '/vendor/autoload.php';

use Altcha\Altcha;

$hmacKey = getenv('ALTCHA_HMAC_KEY') ?: 'your-secret-key-min-32-chars-here!!';

$challenge = Altcha::createChallenge([
    'hmacKey'   => $hmacKey,
    'maxNumber' => 50000,  // Difficulty: higher = slower solve
]);

header('Content-Type: application/json');
echo json_encode($challenge);

Verification

<?php
// In your form handler — verify the proof-of-work solution
require_once __DIR__ . '/vendor/autoload.php';

use Altcha\Altcha;

$hmacKey  = getenv('ALTCHA_HMAC_KEY') ?: 'your-secret-key-min-32-chars-here!!';
$payload  = $_POST['altcha'] ?? '';

$valid = Altcha::verifySolution($payload, $hmacKey);

if (!$valid) {
    $errors[] = 'Verification failed.';
}

Trade-offs: ALTCHA requires no API keys and no account signup — you own everything. However, the proof-of-work computation uses client CPU, which may take 1–3 seconds on older phones. It's also newer and less battle-tested than Turnstile or hCaptcha.

Option 4: Gregwar/Captcha (Direct Securimage Replacement)

If you specifically need a traditional image CAPTCHA — distorted text that users type in — Gregwar/Captcha is the surviving PHP library. It's the closest architectural match to Securimage: server-side image generation via GD, session-based validation, no external dependencies.

Install

composer require gregwar/captcha

Generate and Display

<?php
// captcha.php — generates a CAPTCHA image
session_start();
require_once __DIR__ . '/vendor/autoload.php';

use Gregwar\Captcha\CaptchaBuilder;

$builder = new CaptchaBuilder();
$builder->build(width: 200, height: 50);

// Store the phrase in session for verification
$_SESSION['captcha_phrase'] = $builder->getPhrase();

header('Content-Type: image/jpeg');
$builder->output();

Form

<form method="POST" action="/submit.php">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>

  <img src="/captcha.php" alt="CAPTCHA image — type the text shown">
  <input type="text" name="captcha" required
         autocomplete="off" placeholder="Type the text above">

  <button type="submit">Send</button>
</form>

Verify

<?php
// submit.php — validate the CAPTCHA
session_start();

$expected = $_SESSION['captcha_phrase'] ?? '';
$provided = $_POST['captcha'] ?? '';

// Case-insensitive comparison, then clear the session value
$valid = $expected !== '' && strtolower($provided) === strtolower($expected);
unset($_SESSION['captcha_phrase']);

if (!$valid) {
    $errors[] = 'Incorrect CAPTCHA. Please try again.';
}

Text CAPTCHAs have a 29% human failure rate (ETH Zurich study) and are trivially solved by modern AI. Use Gregwar/Captcha only when you need offline operation or can't depend on any external service. For everything else, the options above offer better security and UX.

Which Should You Pick?

Default choice: Cloudflare Turnstile. Free, invisible, 1M requests/month, no user friction.

Need GDPR certainty: ALTCHA (self-hosted, no data leaves your server) or Friendly Captcha (EU-hosted).

Need visible challenges: hCaptcha. Image challenges provide an audit trail that the user interacted.

Need offline/air-gapped: Gregwar/Captcha. No network dependency at all.

Need the full picture: Read our CAPTCHA alternatives guide for the complete comparison with pricing, privacy analysis, and performance data.