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:
- Architecture changes: Securimage used server-side session storage. Turnstile, hCaptcha, and ALTCHA use token-based verification. Your form handler logic changes from "compare session value" to "POST token to verification endpoint."
- Gregwar/Captcha is the easiest swap — same session-based pattern, just different class names. But you're inheriting the same fundamental weakness: image CAPTCHAs are increasingly solved by AI.
- Check for embedded Securimage: Some CMS plugins (phpIPAM, older WordPress themes) bundle Securimage. Search your codebase for
require.*securimageorSecurimageclass references.
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.