Invisible CAPTCHA in PHP: Every Option Compared

Invisible CAPTCHAs verify users in the background — no puzzles, no checkboxes. The verification happens through browser environment checks, behavioral analysis, or proof-of-work computation. Users see nothing; bots get blocked.

For PHP developers, invisible CAPTCHAs are the sweet spot: they stop automated abuse without the conversion-killing friction of visible challenges. Here's every invisible option, what each actually costs, and working PHP code for each.

Quick Comparison

Solution Truly Invisible? Free Tier Score Management? Self-Hosted?
Cloudflare Turnstile Yes (vast majority of users) 1M/month No — binary pass/fail No
reCAPTCHA v3 Yes (but returns a score you must manage) 10K/month Yes — threshold tuning required No
hCaptcha Passive Yes Not available (Pro $99/mo+) No Enterprise only
ALTCHA Yes (proof-of-work in background) Unlimited (self-hosted) No Yes
Honeypot + timing Yes Unlimited No Yes

Option 1: Cloudflare Turnstile (Recommended)

Turnstile is invisible by default. It analyzes browser signals and Cloudflare's network data, returning a binary pass/fail token. No score to interpret, no threshold to tune.

<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>
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Send</button>
</form>
<?php
// Turnstile server-side verification
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'secret'   => getenv('TURNSTILE_SECRET') ?: '',
        'response' => $_POST['cf-turnstile-response'] ?? '',
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 5,
]);

$data = json_decode(curl_exec($ch) ?: '{}', true);
curl_close($ch);

if (!($data['success'] ?? false)) {
    $errors[] = 'Verification failed.';
}

Why Turnstile wins for most projects: 1M free requests/month, no score management, binary result. See our full Turnstile review for pricing details and limitations.

Option 2: reCAPTCHA v3

reCAPTCHA v3 is invisible but returns a score (0.0 to 1.0) instead of a binary result. You must decide what score is "good enough" and what to do with ambiguous scores. Google recommends loading it on every page for better accuracy — which burns through the 10K/month free tier quickly.

<script src="https://www.google.com/recaptcha/api.js?render=YOUR_V3_KEY"></script>

<form method="POST" action="/submit.php" id="myForm">
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <input type="hidden" name="recaptcha_token" id="recaptchaToken">
  <button type="submit">Send</button>
</form>

<script>
document.getElementById('myForm').addEventListener('submit', function(e) {
  e.preventDefault();
  grecaptcha.ready(function() {
    grecaptcha.execute('YOUR_V3_KEY', {action: 'contact'}).then(function(token) {
      document.getElementById('recaptchaToken').value = token;
      document.getElementById('myForm').submit();
    });
  });
});
</script>
<?php
// reCAPTCHA v3 — verify and interpret score
$ch = curl_init('https://www.google.com/recaptcha/api/siteverify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'secret'   => getenv('RECAPTCHA_V3_SECRET') ?: '',
        'response' => $_POST['recaptcha_token'] ?? '',
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 5,
]);

$data = json_decode(curl_exec($ch) ?: '{}', true);
curl_close($ch);

$score = $data['score'] ?? 0.0;

if ($score >= 0.7) {
    // Likely human — proceed
} elseif ($score >= 0.3) {
    // Ambiguous — escalate (show visible CAPTCHA, email verification, etc.)
} else {
    // Likely bot — reject
    $errors[] = 'Verification failed.';
}

The problem: Score thresholds require ongoing tuning. VPN users score low. New sites get unreliable scores for the first week. And at 10K free assessments/month (post-April 2026 pricing), the cost adds up fast. See our reCAPTCHA v2 vs v3 comparison for the full analysis.

Option 3: hCaptcha Passive Mode

hCaptcha's passive mode works invisibly, similar to Turnstile. But it's only available on the Pro tier ($99/month) and above. The free tier always shows visible image challenges.

If you're paying $99/month for invisible hCaptcha, Turnstile gives you the same invisible experience for free with a larger request allowance. hCaptcha passive mode makes sense only if you're already on hCaptcha Enterprise for other features (on-premise deployment, custom risk scoring). See our hCaptcha review for the full pricing breakdown.

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

ALTCHA runs a cryptographic computation in the user's browser — invisible, no external API, no cookies, no data collection. The browser solves a proof-of-work challenge in 1-3 seconds; your server verifies the solution.

<?php
// ALTCHA server-side verification
require_once __DIR__ . '/vendor/autoload.php';
use Altcha\Altcha;

$hmacKey = getenv('ALTCHA_HMAC_KEY') ?: 'your-secret-key-min-32-chars!!';
$valid   = Altcha::verifySolution($_POST['altcha'] ?? '', $hmacKey);

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

Best for: GDPR-strict projects (no data leaves your server), air-gapped environments, or developers who want zero third-party dependencies. Note that proof-of-work computation uses client CPU — it may take 1-3 seconds on modern devices and longer on older phones, with corresponding battery impact. See our PHP CAPTCHA code examples for the full ALTCHA setup including the challenge endpoint.

Option 5: Honeypot + Timing (No External Service)

For simpler forms, invisible protection without any CAPTCHA service at all. Combine a hidden honeypot field with HMAC-signed timing validation:

This uses the HMAC-signed timing token from our honeypot tutorial. Generate it when rendering the form:

<?php
// Generate when rendering the form
$secret = getenv('FORM_SECRET') ?: 'your-secret-key';
$time = (string) time();
$sig = hash_hmac('sha256', $time, $secret);
$timingToken = $time . '.' . $sig;
?>
<input type="hidden" name="_ts" value="<?= htmlspecialchars($timingToken) ?>">
<?php
// Invisible protection — no external service needed
$isBot = false;

// Honeypot: hidden field that humans skip
if (!empty(trim($_POST['website'] ?? ''))) {
    $isBot = true;
}

// Timing: bots submit instantly, humans take 3+ seconds
$timingToken = $_POST['_ts'] ?? '';
$parts = explode('.', $timingToken, 2);
if (count($parts) === 2) {
    [$time, $sig] = $parts;
    $secret = getenv('FORM_SECRET') ?: 'your-secret-key';
    if (hash_equals(hash_hmac('sha256', $time, $secret), $sig)) {
        $elapsed = time() - (int) $time;
        if ($elapsed < 3) {
            $isBot = true;
        }
    } else {
        $isBot = true; // Tampered token
    }
} else {
    $isBot = true; // Missing token
}

if ($isBot) {
    // Silently reject — return 200 to avoid revealing the trap
    http_response_code(200);
    exit;
}

This stops the majority of simple bot spam with zero external dependencies, zero user friction, and full WCAG compliance. Add Turnstile when bots start getting past it. See our PHP form spam protection guide for the complete layered strategy.

Verdict

For most PHP forms, Cloudflare Turnstile is the best invisible CAPTCHA: free, binary pass/fail, no score management. Use ALTCHA if you need self-hosted/GDPR-strict operation. Use honeypot + timing if you want zero dependencies. Skip hCaptcha passive mode unless you're already on their Enterprise plan — and think twice before choosing reCAPTCHA v3, whose score management overhead and shrinking free tier make it the worst value in the invisible CAPTCHA space.