6.3 KiB
API Reference
Client-Side JavaScript API
The Turnstile JavaScript API is available at window.turnstile after loading the script.
turnstile.render(container, options)
Renders a Turnstile widget into a container element.
Parameters:
container(string | HTMLElement): CSS selector or DOM elementoptions(TurnstileOptions): Configuration object (see configuration.md)
Returns: string - Widget ID for use with other API methods
Example:
const widgetId = window.turnstile.render('#my-container', {
sitekey: 'YOUR_SITE_KEY',
callback: (token) => console.log('Success:', token),
'error-callback': (code) => console.error('Error:', code)
});
turnstile.reset(widgetId)
Resets a widget (clears token, resets challenge state). Useful when form validation fails.
Parameters:
widgetId(string): Widget ID fromrender(), or container element
Returns: void
Example:
// Reset on form error
if (!validateForm()) {
window.turnstile.reset(widgetId);
}
turnstile.remove(widgetId)
Removes a widget from the DOM completely.
Parameters:
widgetId(string): Widget ID fromrender()
Returns: void
Example:
// Cleanup on navigation
window.turnstile.remove(widgetId);
turnstile.getResponse(widgetId)
Gets the current token from a widget (if challenge completed).
Parameters:
widgetId(string): Widget ID fromrender(), or container element
Returns: string | undefined - Token string, or undefined if not ready
Example:
const token = window.turnstile.getResponse(widgetId);
if (token) {
submitForm(token);
}
turnstile.isExpired(widgetId)
Checks if a widget's token has expired (>5 minutes old).
Parameters:
widgetId(string): Widget ID fromrender()
Returns: boolean - True if expired
Example:
if (window.turnstile.isExpired(widgetId)) {
window.turnstile.reset(widgetId);
}
Callback Signatures
type TurnstileCallback = (token: string) => void;
type ErrorCallback = (errorCode: string) => void;
type TimeoutCallback = () => void;
type ExpiredCallback = () => void;
type BeforeInteractiveCallback = () => void;
type AfterInteractiveCallback = () => void;
type UnsupportedCallback = () => void;
Siteverify API (Server-Side)
Endpoint: https://challenges.cloudflare.com/turnstile/v0/siteverify
Request
Method: POST
Content-Type: application/json or application/x-www-form-urlencoded
interface SiteverifyRequest {
secret: string; // Your secret key (never expose client-side)
response: string; // Token from cf-turnstile-response
remoteip?: string; // User's IP (optional but recommended)
idempotency_key?: string; // Unique key for idempotent validation
}
Example:
// Cloudflare Workers
const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
secret: env.TURNSTILE_SECRET,
response: token,
remoteip: request.headers.get('CF-Connecting-IP')
})
});
const data = await result.json();
Response
interface SiteverifyResponse {
success: boolean; // Validation result
challenge_ts?: string; // ISO timestamp of challenge
hostname?: string; // Hostname where widget was solved
'error-codes'?: string[]; // Error codes if success=false
action?: string; // Action name from widget config
cdata?: string; // Custom data from widget config
}
Example Success:
{
"success": true,
"challenge_ts": "2024-01-15T10:30:00Z",
"hostname": "example.com",
"action": "login",
"cdata": "user123"
}
Example Failure:
{
"success": false,
"error-codes": ["timeout-or-duplicate"]
}
Error Codes
| Code | Cause | Solution |
|---|---|---|
missing-input-secret |
Secret key not provided | Include secret in request |
invalid-input-secret |
Secret key is wrong | Check secret key in dashboard |
missing-input-response |
Token not provided | Include response token |
invalid-input-response |
Token is invalid/malformed | Verify token from widget |
timeout-or-duplicate |
Token expired (>5min) or reused | Generate new token, validate once |
internal-error |
Cloudflare server error | Retry with exponential backoff |
bad-request |
Malformed request | Check JSON/form encoding |
TypeScript Types
interface TurnstileOptions {
sitekey: string;
action?: string;
cData?: string;
callback?: (token: string) => void;
'error-callback'?: (errorCode: string) => void;
'expired-callback'?: () => void;
'timeout-callback'?: () => void;
'before-interactive-callback'?: () => void;
'after-interactive-callback'?: () => void;
'unsupported-callback'?: () => void;
theme?: 'light' | 'dark' | 'auto';
size?: 'normal' | 'compact' | 'flexible';
tabindex?: number;
'response-field'?: boolean;
'response-field-name'?: string;
retry?: 'auto' | 'never';
'retry-interval'?: number;
language?: string;
execution?: 'render' | 'execute';
appearance?: 'always' | 'execute' | 'interaction-only';
'refresh-expired'?: 'auto' | 'manual' | 'never';
}
interface Turnstile {
render(container: string | HTMLElement, options: TurnstileOptions): string;
reset(widgetId: string): void;
remove(widgetId: string): void;
getResponse(widgetId: string): string | undefined;
isExpired(widgetId: string): boolean;
execute(container?: string | HTMLElement, options?: TurnstileOptions): void;
}
declare global {
interface Window {
turnstile: Turnstile;
onloadTurnstileCallback?: () => void;
}
}
Script Loading
<!-- Standard -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<!-- Explicit render mode -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
<!-- With load callback -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback"></script>
<script>
window.onloadTurnstileCallback = () => {
window.turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY' });
};
</script>