NEXAFORGE.STUDIO

BACKEND GUIDE · SITEVERIFY

Cloudflare Turnstile Server-Side Validation Examples

Siteverify is the security boundary in a Turnstile integration. The backend sends the secret and browser token to Cloudflare, evaluates the response and only then performs the protected action.

Required request fields and response checks

Send the secret key and the response token in a POST request to Cloudflare's Siteverify endpoint. The remote IP and an idempotency key are optional fields for workflows that need them.

At minimum, require success:true. In a production integration, also compare hostname and action when they are part of your widget configuration, and log error codes at a level that helps operators without recording secrets or full personal form content.

  • ▸ secret: server-side key
  • ▸ response: token received from the browser
  • ▸ remoteip: optional visitor address
  • ▸ idempotency_key: optional UUID for retry protection

Node.js example

Keep validation in a small function with a timeout and a narrow return type. The caller should treat exceptions and non-success responses as rejection, then decide how much detail is safe to show the user.

export async function verifyTurnstile(token: string, remoteIp?: string) {
  const body = new URLSearchParams({
    secret: process.env.TURNSTILE_SECRET_KEY!,
    response: token,
  });
  if (remoteIp) body.set("remoteip", remoteIp);

  const response = await fetch(
    "https://challenges.cloudflare.com/turnstile/v0/siteverify",
    { method: "POST", body, signal: AbortSignal.timeout(5000) }
  );
  if (!response.ok) return { success: false, reason: "upstream" };

  const result = await response.json();
  return { success: result.success === true, result };
}

Python example

The same pattern applies in Flask, Django or FastAPI: validate before the protected work, set a bounded timeout and reject malformed upstream responses.

import os
import requests

def verify_turnstile(token, remote_ip=None):
    payload = {
        "secret": os.environ["TURNSTILE_SECRET_KEY"],
        "response": token,
    }
    if remote_ip:
        payload["remoteip"] = remote_ip
    response = requests.post(
        "https://challenges.cloudflare.com/turnstile/v0/siteverify",
        data=payload,
        timeout=5,
    )
    response.raise_for_status()
    result = response.json()
    return result.get("success") is True, result

Token expiry and replay are expected states

Tokens expire after 300 seconds and are single-use. A timeout-or-duplicate response can mean the user waited too long, the browser sent the same token twice, or the application retried after Siteverify had already consumed it.

Return a retryable message and reset the client widget. Do not loop the same token back to Siteverify. For payments or account creation, use a separate application idempotency key so a fresh Turnstile token does not repeat the business action.

Define the upstream failure policy

If Siteverify times out or returns an invalid body, high-risk endpoints should fail closed. A low-risk newsletter form might choose a queue or controlled retry, but that is a business decision and should not happen accidentally because an exception handler defaults to success.

Use short timeouts, track upstream failure rate and distinguish verification service failures from invalid visitor tokens.

Log enough to debug, not enough to leak

Record the endpoint, expected action, result, error codes, request correlation ID and coarse timing. Do not log the secret key. Avoid storing the full response token because it is unnecessary and can turn logs into a credential repository.

If remote IP is used, apply the same privacy and retention rules as the rest of the application logs.

  • ▸ Never include the secret in client code
  • ▸ Redact tokens from request logging
  • ▸ Monitor spikes in timeout or invalid-input errors
  • ▸ Separate user-facing messages from operator diagnostics

Frequently asked questions

What is the Cloudflare Turnstile Siteverify endpoint?

Send a POST request to https://challenges.cloudflare.com/turnstile/v0/siteverify with the server secret and the browser response token.

What should the backend do if Siteverify is unavailable?

For security-sensitive endpoints, reject or safely defer the request rather than treating the failure as success. Set a bounded timeout and monitor upstream errors.

Can the same token be validated twice?

No. Turnstile tokens are single-use. A replay or a token older than five minutes is rejected and the client should obtain a new token.

Related service and guides

Need production help? Email [email protected] or contact @taoquan8 on Telegram.