Validate the token·续

var validation = await _turnstileService.ValidateTokenAsync(cfTurnstileResponse, remoteip);

if (validation.Success) { // Valid token - process form return Ok("Form submitted successfully"); } else { // Invalid token - reject submission return BadRequest($"Verification failed: {string.Join(", ", validation.ErrorCodes)}"); } }


---

## Advanced validation techniques

*Idempotency keys for retry operationjs*

```js
const crypto = require("crypto");

async function validateWithRetry(token, remoteip, maxRetries = 3) {
    const idempotencyKey = crypto.randomUUID();

for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            const formData = new FormData();
            formData.append("secret", SECRET_KEY);
            formData.append("response", token);
            formData.append("remoteip", remoteip);
            formData.append("idempotency_key", idempotencyKey);

const response = await fetch(
                "https://challenges.cloudflare.com/turnstile/v0/siteverify",
                {
                    method: "POST",
                    body: formData,
                },
            );

const result = await response.json();

if (response.ok) {
                return result;
            }

// If this is the last attempt, return the error
            if (attempt === maxRetries) {
                return result;
            }

// Wait before retrying (exponential backoff)
            await new Promise((resolve) =>
                setTimeout(resolve, Math.pow(2, attempt) * 1000),
            );
        } catch (error) {
            if (attempt === maxRetries) {
                return { success: false, "error-codes": ["internal-error"] };
            }
        }
    }
}

Enhanced validation with custom checksjs

async function validateTurnstileEnhanced(
    token,
    remoteip,
    expectedAction = null,
    expectedHostname = null,
) {
    const validation = await validateTurnstile(token, remoteip);

if (!validation.success) {
        return {
            valid: false,
            reason: "turnstile_failed",
            errors: validation["error-codes"],
        };
    }

// Check if action matches expected value (if specified)
    if (expectedAction && validation.action !== expectedAction) {
        return {
            valid: false,
            reason: "action_mismatch",
            expected: expectedAction,
            received: validation.action,
        };
    }

// Check if hostname matches expected value (if specified)
    if (expectedHostname && validation.hostname !== expectedHostname) {
        return {
            valid: false,
            reason: "hostname_mismatch",
            expected: expectedHostname,
            received: validation.hostname,
        };
    }

// Check token age (warn if older than 4 minutes)
    const challengeTime = new Date(validation.challenge_ts);
    const now = new Date();
    const ageMinutes = (now - challengeTime) / (1000 * 60);

if (ageMinutes > 4) {
        console.warn(`Token is ${ageMinutes.toFixed(1)} minutes old`);
    }

return {
        valid: true,
        data: validation,
        tokenAge: ageMinutes,
    };
}

// Usage
const result = await validateTurnstileEnhanced(
    token,
    remoteip,
    "login", // expected action
    "example.com", // expected hostname
);

if (result.valid) {
    // Process the request
    console.log("Validation successful:", result.data);
} else {
    // Handle validation failure
    console.log("Validation failed:", result.reason);
}

API response format

Examplejson

{
  "success": true,
  "challenge_ts": "2022-02-28T15:14:30.096Z",
  "hostname": "example.com",
  "error-codes": [],
  "action": "login",
  "cdata": "sessionid-123456789",
  "metadata": {
    "ephemeral_id": "x:9f78e0ed210960d7693b167e"
  }
}

Examplejson

{
  "success": false,
  "error-codes": ["invalid-input-response"]
}

Response fields

Field Description
success Boolean indicating if validation was successful
challenge_ts ISO 8601 timestamp when the challenge was solved
hostname Hostname where the challenge was served
error-codes Array of error codes (if validation failed)
action Custom action identifier from client-side
cdata Custom data payload from client-side
metadata.ephemeral_id Device fingerprint ID (Enterprise only)

Error codes reference

Error code Description Action required
missing-input-secret Secret parameter not provided Ensure secret key is included
invalid-input-secret Secret key is invalid or expired Check your secret key in the Cloudflare dashboard
missing-input-response Response parameter was not provided Ensure token is included
invalid-input-response Token is invalid, malformed, or expired User should retry the challenge
bad-request Request is malformed Check request format and parameters
timeout-or-duplicate Token has already been validated Each token can only be used once
internal-error Internal error occurred Retry the request

Implementation

Example implementationjs

class TurnstileValidator {
    constructor(secretKey, timeout = 10000) {
        this.secretKey = secretKey;
        this.timeout = timeout;
    }

async validate(token, remoteip, options = {}) {
        // Input validation
        if (!token || typeof token !== "string") {
            return { success: false, error: "Invalid token format" };
        }

if (token.length > 2048) {
            return { success: false, error: "Token too long" };
        }

// Prepare request
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.timeout);

try {
            const formData = new FormData();
            formData.append("secret", this.secretKey);
            formData.append("response", token);

if (remoteip) {
                formData.append("remoteip", remoteip);
            }

if (options.idempotencyKey) {
                formData.append("idempotency_key", options.idempotencyKey);
            }

const response = await fetch(
                "https://challenges.cloudflare.com/turnstile/v0/siteverify",
                {
                    method: "POST",
                    body: formData,
                    signal: controller.signal,
                },
            );

const result = await response.json();

// Additional validation
            if (result.success) {
                if (
                    options.expectedAction &&
                    result.action !== options.expectedAction
                ) {
                    return {
                        success: false,
                        error: "Action mismatch",
                        expected: options.expectedAction,
                        received: result.action,
                    };
                }

if (
                    options.expectedHostname &&
                    result.hostname !== options.expectedHostname
                ) {
                    return {
                        success: false,
                        error: "Hostname mismatch",
                        expected: options.expectedHostname,
                        received: result.hostname,
                    };
                }
            }

return result;
        } catch (error) {
            if (error.name === "AbortError") {
                return { success: false, error: "Validation timeout" };
            }

console.error("Turnstile validation error:", error);
            return { success: false, error: "Internal error" };
        } finally {
            clearTimeout(timeoutId);
        }
    }
}

// Usage
const validator = new TurnstileValidator(process.env.TURNSTILE_SECRET_KEY);

const result = await validator.validate(token, remoteip, {
    expectedAction: "login",
    expectedHostname: "example.com",
});

if (result.success) {
    // Process the request
} else {
    // Handle failure
    console.log("Validation failed:", result.error);
}

Testing

You can test the dummy token generated with testing sitekey via Siteverify API with the testing secret key. Your production secret keys will reject dummy tokens.

Refer to Testing for more information.


Best practices

Security

  • Store your secret keys securely. Use environment variables or secure key management.
  • Validate the token on every request. Never trust client-side validation alone.
  • Check additional fields. Validate the action and hostname when specified.
  • Monitor for abuse and log failed validations and unusual patterns.
  • Use HTTPS. Always validate over secure connections.
  • Only call the Siteverify API in your backend environment. If you expose the secret key in the front-end client code to call Siteverify, attackers can bypass the security check. Ensure that your client-side code sends the validation token to your backend, and that your backend is the sole caller of the Siteverify API.

Performance

  • Set reasonable timeouts. Do not wait indefinitely for Siteverify responses.
  • Implement retry logic and handle temporary network issues.
  • Cache validation results for the same token, if it is needed for your flow.
  • Monitor your API latency. Track the Siteverify response time.

Error handling

  • Have fallback behavior for API failures.
  • Use user-friendly messaging. Do not expose internal error details to users.
  • Properly log errors for debugging without exposing secrets.
  • Rate limit to protect against validation flooding.

Was this helpful?

YesNo

On this page

Validate the token·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#page","headline":"Validate the token","description":"Validate Turnstile tokens on your server with the siteverify API.","url":"https://developers.cloudflare.com/turnstile/get-started/server-side-validation/","inLanguage":"en","image":"https://developers.cloudflare.com/turnstile/get-started/server-side-validation/og.png?v=58220cfac6c19059","dateModified":"2026-10-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["REST API"]}


description: Create and manage Turnstile widgets using the Cloudflare API. title: Create and manage widgets using Cloudflare API image: https://developers.cloudflare.com/turnstile/get-started/widget-management/api/og.png?v=3ee3163dce753625


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/turnstile/llms.txt
Use this file to discover all available pages before exploring further.