How do make verification mandatory with cloudflare turnstile

I have such a code and when I press subimte on the site, it sends it directly, but I want it to be sent if it is correct.

!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="" async defer></script>

   <div class="cf-turnstile" data-sitekey="key"></div>


What do you mean? It can’t be verified until you submit the forum


No, now, for example, I have a form, it verifies normally, but it verifies and sends it and when I open the vpn, it says verification is required and asks me to click, normally, but it sends it directly to the submite without verifying it, I want the firstly verified ones to be able to submit, so most of the sites I use, right?


To use Turnstile (and other CAPTCHA’s) correctly, you don’t just need to add the code to the frontend, you need to verify it in the backend too.

When the Turnstile challenge is completed and the form is submitted, the Turnstile widget adds a parameter cf-turnstile-response to the response. In the PHP code where the form is submitted to, you’ll need to check that this parameter is present, and send it to Cloudflare, along with your site secret key, to verify the CAPTCHA was successful.

The Cloudflare instructions have a page for both “Client-side rendering” and “Server-side validation”. You need both to use Turnstile. You’ve done the first part, now it’s time for the second one.

There does not appear to be an official PHP client for it, but you can find some examples and third party libraries online. Or just write your own based on the Javascript example in the official docs, it’s a pretty simple API.


Hi Admin and walterwick,

Ok, let’s code then. :+1:


$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, '');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
	'secret' => SECRET_KEY,
	'response' => $_POST['cf-turnstile-response'],
	'remoteip' => $ip,
$output = curl_exec($ch);

$response = json_decode($output, true);
if(json_last_error() !== JSON_ERROR_NONE){
	// cannot verify at this time, make sure to let user know it's not their fault
if(!(is_array($response) && sizeof($response) > 0)){
	// verification fail, you wont't get [success] if it's not an array in the first place
if(sizeof(array_diff(['success','hostname','error-codes','challenge_ts'], array_keys($response))) > 0){
	// verification fail, not all required fields exists
if(	!!$response['success'] &&
	$response['hostname'] === 'your host name here' &&
	strtotime('now') - strtotime($response['challenge_ts']) < YOUR_ACCEPTED_DELAY){
	// success

Welp, it gets the job done. Personally would use an OOP class to handle this but since I’m not sure how Walter’s site is developed, the above should serve as a starting reference.

I have another class that returns the IP for me so I’ll assign that to $ip, if such a function does not exist then you can use this:


$headers = getallheaders();
$headers = array_change_key_case($headers, CASE_LOWER);
if(array_key_exists('cf-connecting-ip', $headers)){
	$ip = $headers['cf-connecting-ip'];

In the worst-case scenario that you somehow aren’t using Cloudflare for your site and you do not have that header, here’s some old code I used back a decade ago somewhere when PHP 4 was still a thing.

Just before you copy and paste, do note that certain headers with this method are not secure anymore as headers can be spoofed, even to put local IP addresses, so only take it as a reference. I believe Cloudflare should also be able to identify this on their end if you simply pass through the IP, it will also be an indicator for them to reject the request and protect you.


$ip = '';
if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)){
else if(array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){
else if(array_key_exists('HTTP_X_FORWARDED', $_SERVER)){
else if(array_key_exists('HTTP_FORWARDED_FOR', $_SERVER)){
else if(array_key_exists('HTTP_FORWARDED', $_SERVER)){
else if(array_key_exists('REMOTE_ADDR', $_SERVER)){

    // At this point, you might as well drop the request without even pinging CF.

For OOP and MVC, just place this into an appropriate class and call the function your way.



thank you chiucs123 and admin

<div class="container mt-5">
    <h1>Ä°letiĹźim Formu</h1>
    <form action="javascript:void(0);" method="post" enctype="multipart/form-data" onsubmit="submitForm()">
        <div class="form-group">
            <label for="name">Ad Soyad:</label>
            <input type="text" class="form-control" id="name" name="name" required>
        <div class="form-group">
            <label for="email">E-posta Adresi:</label>
            <input type="email" class="form-control" id="email" name="email" required>
        <div class="form-group">
            <label for="subject">Konu:</label>
            <input type="text" class="form-control" id="subject" name="subject" required>
        <div class="form-group">
            <label for="message">Mesaj:</label>
            <textarea class="form-control" id="message" name="message" required></textarea>
        <div class="form-group">
            <label for="attachment">Dosya/Görsel Ekle (Maksimum 2MB):</label>
            <input type="file" class="form-control-file" id="attachment" name="attachment">
        <button type="submit" class="btn btn-primary">Gönder</button>

    function submitForm() {
        var name = document.getElementById("name").value;
        var email = document.getElementById("email").value;
        var subject = document.getElementById("subject").value;
        var message = document.getElementById("message").value;
        var attachment = document.getElementById("attachment").files[0];

        var formData = new FormData();
        formData.append("caption", "Yeni iletişim mesajı:\n\nAd Soyad: " + name + "\nE-posta: " + email + "\nKonu: " + subject + "\nMesaj: " + message);

        if (attachment) {
            formData.append("photo", attachment);

        fetch("" + encodeURIComponent("Yeni iletişim mesajı:\n\nAd Soyad: " + name + "\nE-posta: " + email + "\nKonu: " + subject + "\nMesaj: " + message), {
            method: "POST"
        .then(function(response) {
            if (response.ok) {
                swal("Mesajınız Gönderildi", "Teşekkür ederiz!", "success")
                .then((value) => {
                    // Ä°sterseniz baĹźka bir iĹźlem ekleyebilirsiniz.
                    // window.location.href = 'index.php'; // Anasayfaya yönlendir
            } else {
                swal("Hata", "Mesajınız gönderilemedi. Lütfen daha sonra tekrar deneyin.", "error");
                // İsterseniz hata durumlarına uygun işlemler ekleyebilirsiniz.
        .catch(function(error) {
            swal("Hata", "Bir hata oluĹźtu: " + error, "error");

I have such a code and it sends the message to the telegram api, there is no problem here, but I am new to php and I don’t understand the code very much. If I put that code into the form, if the captcha is not filled in, will it prevent it from being sent?
thank you 3>

In this code, the Telegram bot message is being sent from the browser. If so, there is absolutely nothing you can do to stop the spammer from editing the code to disable all validation checks, or just grab the bot credentials from the page and start spamming without using the form anyways.

In other words: CAPTCHA’s are pretty much pointless for anything other than cosmetic purposes because there is no way to stop people from bypassing it.

If you want to make it secure, you should change the frontend code so it sends the submitted data to PHP code on the server, where you can check the submitted data (including the CAPTCHA response), and only send the message is everything is OK.

That said, please note that the Telegram APIs are blocked on our free hosting servers, so you won’t be able to reproduce the functionality in PHP here. Premium hosting doesn’t have this restriction.

But you could have your form send an email instead using SMTP. Or use an intermediary API to talk to Telegram, such as Zapier.


thank you admin.

ı love this comminty.

it’s best to keep it as it is, have a good day.


This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.