Come Implementare Cloudflare Turnstile Captcha in PHP

Come Implementare Cloudflare Turnstile Captcha in PHP

14 Gen. 2026

Mi serviva Cloudflare Turnstile in un progetto PHP, e non trovavo un esempio senza framework. La documentazione di Cloudflare è completa, ma gli esempi mostravano quasi sempre l'integrazione in JavaScript o davano per scontato un framework preciso. Mi serviva qualcosa di base, adattabile a qualsiasi progetto PHP, così ho scritto questa guida e pubblicato il codice.

Turnstile è un'alternativa ai CAPTCHA tradizionali: non chiede di risolvere puzzle o riconoscere semafori, lavora in background e verifica che il visitatore sia umano senza attrito. E non serve far passare il traffico da Cloudflare per usarlo. Il form contatti di questo sito usa esattamente questo controllo lato server, dentro l'API Laravel.

Prerequisiti

Prima del codice servono le chiavi. Dalla dashboard di Cloudflare, nella sezione Turnstile, crea un nuovo sito e registra il tuo dominio. Ricevi due chiavi: la Site Key, che va nell'HTML del frontend, e la Secret Key, che resta sul server per la verifica.

Il frontend

Il form HTML con il widget Turnstile è sorprendentemente semplice: includi lo script nell'head, aggiungi il div cf-turnstile dove vuoi il widget e metti la tua Site Key al posto del segnaposto.

<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Esempio Cloudflare Turnstile</title>
    <!-- Carica lo Script Cloudflare Turnstile -->
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
    <h1>Form di Contatto</h1>
    
    <form action="submit-example.php" method="POST">
        <label for="name">Nome:</label>
        <input type="text" id="name" name="name" required>
        
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required>
        
        <label for="message">Messaggio:</label>
        <textarea id="message" name="message" required></textarea>
        
        <!-- Widget Cloudflare Turnstile -->
        <div class="cf-turnstile" data-sitekey="TUA_SITE_KEY_QUI"></div>
        
        <button type="submit">Invia</button>
    </form>
</body>
</html>

Quando il form viene inviato, Turnstile aggiunge un campo nascosto cf-turnstile-response con un token.

La validazione lato server

È la parte che molte guide saltano, ed è quella che conta: senza verifica lato server, un utente malevolo può inviare il form bypassando del tutto il CAPTCHA. Lo script prende il token, lo manda all'endpoint di verifica di Cloudflare insieme alla Secret Key con cURL, e processa i dati del form solo se la verifica va a buon fine.

<?php
// submit-example.php

// Controlla se il form è stato inviato
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
    // Ottieni il token di risposta di Turnstile
    $turnstileResponse = $_POST['cf-turnstile-response'] ?? '';
    
    // La tua Secret Key da Cloudflare
    $secretKey = 'TUA_SECRET_KEY_QUI';
    
    // Endpoint di verifica Cloudflare Turnstile
    $verifyUrl = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
    
    // Prepara i dati per la verifica
    $data = [
        'secret' => $secretKey,
        'response' => $turnstileResponse,
        'remoteip' => $_SERVER['REMOTE_ADDR'] // Opzionale ma consigliato
    ];
    
    // Inizializza cURL
    $curl = curl_init();
    
    curl_setopt_array($curl, [
        CURLOPT_URL => $verifyUrl,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query($data),
        CURLOPT_RETURNTRANSFER => true
    ]);
    
    // Esegui la richiesta
    $response = curl_exec($curl);
    $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
    curl_close($curl);
    
    // Analizza la risposta JSON
    $result = json_decode($response, true);
    
    // Controlla se la verifica è stata positiva
    if ($httpCode === 200 && isset($result['success']) && $result['success'] === true) {
        // Verifica CAPTCHA superata!
        
        // Elabora i dati del form qui
        $name = htmlspecialchars($_POST['name']);
        $email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
        $message = htmlspecialchars($_POST['message']);
        
        // La tua logica di elaborazione del form
        echo "Grazie, " . $name . "! Il tuo messaggio è stato ricevuto.";
        
        // Invia email, salva nel database, ecc.
        
    } else {
        // Verifica CAPTCHA fallita
        
        $errorCodes = $result['error-codes'] ?? ['unknown-error'];
        echo "Verifica CAPTCHA fallita. Codici errore: " . implode(', ', $errorCodes);
        
        // Registra l'errore o reindirizza al form
    }
    
} else {
    // Gestisci richieste non-POST
    header('Location: form-example.php');
    exit;
}
?>

I codici di errore

Se la verifica fallisce, Turnstile restituisce un codice di errore. I più comuni: missing-input-secret e invalid-input-secret (Secret Key mancante o errata), missing-input-response e invalid-input-response (token mancante, errato o scaduto), timeout-or-duplicate (token già validato o scaduto).

Qualche regola di sicurezza

  • La Secret Key non esce mai dal server: tienila in una variabile d'ambiente o in un file di configurazione protetto.
  • Valida sempre lato server: la validazione solo lato client si aggira.
  • Passa anche l'IP del client: aiuta Cloudflare a riconoscere pattern sospetti.
  • Usa sempre HTTPS.

Personalizzare il widget

Il widget accetta alcune opzioni via attributi data-: tema (light, dark, auto), dimensione, lingua e comportamento.

<div class="cf-turnstile" 
     data-sitekey="TUA_SITE_KEY_QUI"
     data-theme="light"
     data-size="normal"
     data-language="it">
</div>

Per lo sviluppo Cloudflare mette a disposizione delle chiavi di test che passano sempre, utili per provare l'integrazione prima di mettere quelle vere.

Il codice completo, con il form e lo script di invio pronti all'uso, è nel repository su GitHub. Per tutto il resto c'è la documentazione ufficiale di Turnstile.

Altri articli: