Flick
Guide Validation Services API Pro Examples GitHub
Docs / Services
Flick Pro Service

Tested and maintained, so you don't have to. $99/year.

Get Flick Pro

Turnstile

Cloudflare Turnstile integration with configurable timeout and multiple HTTP transport methods.

Why Use This Service?

What you'd build yourself: Cloudflare API integration, widget rendering, server-side token verification, and proper error handling.

Maintained for you: tracks Cloudflare's API changes so your integration doesn't break.

What Pro provides:

  • Privacy-focused alternative to reCAPTCHA (no cookies required)
  • Simple scripts() and passed() API
  • Automatic widget rendering
  • Works with file_get_contents or cURL

Installation

Flick Pro requires a license. See Pro installation for setup instructions.

Configuration

Get your Turnstile keys from the Cloudflare Dashboard.

$config = [
    'services' => [
        'turnstile' => [
            'siteKey' => 'your-site-key',
            'secretKey' => 'your-secret-key'
        ]
    ]
];

$form = new Flick($config);

Full Configuration

$config = [
    'services' => [
        'turnstile' => [
            'siteKey' => 'your-site-key',
            'secretKey' => 'your-secret-key',
            'timeout' => 10,        // API request timeout in seconds
            'curl' => false,        // Use cURL instead of file_get_contents
            'expectedAction' => 'submit',        // Reject tokens minted for a different action
            'expectedHostname' => 'example.com'  // Reject tokens minted on another host
        ]
    ]
];

expectedAction and expectedHostname are both optional and both off unless you set them. Turning them on stops a token issued elsewhere on your site — or on someone else's copy of your page — from being replayed against this form.

expectedAction must be 1–32 characters of letters, numbers, underscores or hyphens; anything else is rejected when the form is built, rather than silently failing every submission. Flick renders it onto the widget for you, so the action Cloudflare mints the token for is always the one Flick checks it against — you do not add data-action yourself.

Basic Usage

1. Add Scripts to Your Page

Include the Turnstile widget in your HTML:

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'turnstile' => [
            'siteKey' => 'your-site-key',
            'secretKey' => 'your-secret-key'
        ]
    ]
]);
?>
<!DOCTYPE html>
<html>
<head>
    <title>Contact Form</title>
</head>
<body>
    <?php $form->open(); ?>
    <?php $form->text('name', 'Name'); ?>
    <?php $form->text('email', 'Email'); ?>
    <?php $form->textarea('message', 'Message'); ?>

    <!-- The widget must be INSIDE the form, before the submit button —
         Turnstile only submits its response token from inside the <form> -->
    <?= $form->turnstile->scripts() ?>

    <?php $form->submit(); ?>
    <?php $form->close(); ?>
</body>
</html>

Build the form with open()/close() and the individual field methods (rather than the one-shot create()) so the widget can be placed inside the <form> element. create() renders a complete, already-closed form — a widget printed after it sits outside the form, its token is never submitted, and passed() always fails with "Missing Turnstile response token".

2. Validate on Submission

Check if the Turnstile validation passed:

if ($form->submitted()) {
    if (!$form->turnstile->passed()) {
        $form->addError('turnstile', 'Security check failed. Please try again.');
    }

    if ($form->ok()) {
        $request = $form->request('Name[required], Email[required, email], Message[required]');

        if ($form->ok()) {
            // Process form...
        }
    }
}

Methods

scripts()

Generate the Turnstile JavaScript and widget HTML for form integration.

$form->turnstile->scripts(): string

Returns: HTML script tag and widget div that:

  1. Load the Cloudflare Turnstile library
  2. Render the widget in your form

Tip

Place the widget inside the <form> element, typically before the submit button. Cloudflare only submits the response token when the widget sits inside the form it belongs to.

passed()

Validate the Turnstile response from form submission.

$form->turnstile->passed(): bool

Returns: true if validation passed, false otherwise.

Validation checks:

  1. Request method is POST
  2. Turnstile response token is present
  3. Cloudflare API verification succeeds
  4. Hostname matches expectedHostname (if configured)
  5. Action matches expectedAction (if configured)

When any check fails, passed() also records the reason in Flick's error bag under the key turnstile — for example Missing Turnstile response token. Adding your own addError('turnstile', ...) replaces that message with yours.

Complete Example

<?php
require 'vendor/autoload.php';

$form = new Flick\Flick([
    'services' => [
        'turnstile' => [
            'siteKey' => $_ENV['TURNSTILE_SITE_KEY'],
            'secretKey' => $_ENV['TURNSTILE_SECRET_KEY']
        ],
        'mail' => [
            'fromAddress' => 'noreply@example.com',
            'mailer' => [
                'transport' => 'smtp',
                'host' => 'smtp.example.com',
                'port' => 587,
                'username' => 'user',
                'password' => 'pass'
            ]
        ]
    ]
]);

// Handle form submission
if ($form->submitted()) {
    // Validate Turnstile first
    if (!$form->turnstile->passed()) {
        $form->addError('form', 'Security check failed. Please try again.');
    }

    if ($form->ok()) {
        $request = $form->request('
            Name[required, min:2],
            Email[required, email],
            Message[required, min:10]
        ');

        if ($form->ok()) {
            // Send email
            $form->mail->sendFormData(
                'admin@example.com',
                'New Contact Form Submission',
                $request
            );

            $form->successMessage('Thank you! Your message has been sent.');
        }
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>Contact Us</title>
</head>
<body>
    <h1>Contact Us</h1>

    <?php if ($form->errorsIsNotEmpty()): ?>
        <div class="errors">
            <ul>
                <?php foreach ($form->getErrors() as $error): ?>
                    <li><?= htmlspecialchars($error) ?></li>
                <?php endforeach; ?>
            </ul>
        </div>
    <?php endif; ?>

    <?php $form->open(); ?>
    <?php $form->text('name', 'Name'); ?>
    <?php $form->text('email', 'Email'); ?>
    <?php $form->textarea('message', 'Message'); ?>

    <!-- Inside the form, so the response token is submitted with it -->
    <?= $form->turnstile->scripts() ?>

    <?php $form->submit(); ?>
    <?php $form->close(); ?>
</body>
</html>

How It Works

Client Side

When scripts() is called, it generates:

  1. A script tag that loads Cloudflare's Turnstile library
  2. A div with the cf-turnstile class and your site key

The widget automatically:

  • Renders the challenge (if needed)
  • Adds a hidden cf-turnstile-response field to the form it sits inside (which is why the widget must be placed inside your <form> element)

Server Side

When passed() is called, it:

  1. Checks the request is a POST request
  2. Extracts the cf-turnstile-response token
  3. Sends the token to Cloudflare's verification API
  4. Returns true if verification succeeds

Turnstile vs reCAPTCHA

Feature Turnstile reCAPTCHA v3
Visible widget Yes (non-intrusive) No (invisible)
Score-based No Yes
Privacy No cookies required Uses cookies
Free tier Unlimited 1M/month

Choose Turnstile for a privacy-focused, simpler integration. Choose reCAPTCHA for score-based bot detection.

Using cURL

By default, Flick uses file_get_contents for API requests. To use cURL instead:

'turnstile' => [
    'siteKey' => 'your-site-key',
    'secretKey' => 'your-secret-key',
    'curl' => true
]

Tip

Use cURL if allow_url_fopen is disabled in your PHP configuration.

Security Features

  • Server-Side Verification: Tokens are verified with Cloudflare's API
  • IP Validation: Client IP is sent for additional verification
  • Secure API Communication: SSL verification enabled for API requests
  • Configurable Timeouts: Prevents hanging requests