Add CAPTCHA Protection to a PHP Form with Turnstile

Last updated: August 25, 2026.

Bot protection requires two steps: a browser widget produces a short-lived token, and the PHP form handler validates that token with the provider before accepting the submission. Client-side validation alone provides no protection.

Add the widget to the form

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form action="submit.php" method="post">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Submit</button>
</form>

Validate the token in PHP

<?php
declare(strict_types=1);

$token = (string) ($_POST['cf-turnstile-response'] ?? '');
$curl = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'secret' => $_ENV['TURNSTILE_SECRET_KEY'],
        'response' => $token,
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
]);

$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$result = is_string($body) ? json_decode($body, true) : null;

if ($status !== 200 || !is_array($result) || ($result['success'] ?? false) !== true) {
    http_response_code(400);
    exit('Verification failed. Please try again.');
}

// Validate the form, apply CSRF protection and rate limits, then process it.

Keep the secret key on the server, validate every token once, and fail closed when validation cannot be completed. CAPTCHA complements rate limiting and form validation; it does not replace them. Cloudflare documents the required server-side Turnstile validation.

admin

admin

Leave a Reply

Your email address will not be published.