Turnstile CAPTCHA For WooCommerceTurnstile CAPTCHA For WooCommerce
Buy Now
View Demo
  • Getting Started

    • Introduction
    • Quick Start
    • Features
    • Installation
    • First-Time Setup
    • Get Turnstile Keys
    • Onboarding Wizard
  • Settings

    • Settings Overview
    • API Settings
    • General
    • Design Studio
    • Conditional Rules
    • Per-Form Config
    • Notifications
  • Supported Forms

    • All Supported Forms
    • WooCommerce Forms
    • WordPress Forms
    • Third-Party Form Plugins
    • Checkout Blocks
    • Shortcode
  • Protection & Monitoring

    • Analytics Dashboard
    • Rate Limiting
    • Recovery URL
    • Email Digest
    • Webhooks
  • Developer

    • REST API
    • Filters & Hooks
    • Site Health
  • Compare

    • vs reCAPTCHA
    • vs hCaptcha
  • Help

    • Troubleshooting
    • FAQ
    • Glossary
Support
Buy Now
View Demo
  • Getting Started

    • Introduction
    • Quick Start
    • Features
    • Installation
    • First-Time Setup
    • Get Turnstile Keys
    • Onboarding Wizard
  • Settings

    • Settings Overview
    • API Settings
    • General
    • Design Studio
    • Conditional Rules
    • Per-Form Config
    • Notifications
  • Supported Forms

    • All Supported Forms
    • WooCommerce Forms
    • WordPress Forms
    • Third-Party Form Plugins
    • Checkout Blocks
    • Shortcode
  • Protection & Monitoring

    • Analytics Dashboard
    • Rate Limiting
    • Recovery URL
    • Email Digest
    • Webhooks
  • Developer

    • REST API
    • Filters & Hooks
    • Site Health
  • Compare

    • vs reCAPTCHA
    • vs hCaptcha
  • Help

    • Troubleshooting
    • FAQ
    • Glossary
Support
  • Getting Started

    • Introduction
    • Quick Start — Turnstile Live in 5 Minutes
    • Features — Everything the Plugin Can Do
    • Installation — Full Setup Guide
    • First-Time Setup
    • Get Turnstile Keys from Cloudflare
    • Onboarding Wizard
  • Settings

    • Settings Overview — All 9 Tabs
    • API Settings Tab
    • General Settings Tab
    • Design Studio Tab
    • Conditional Rules Tab
    • Per-Form Config Tab
    • Notifications Tab
  • Supported Forms

    • All Supported Forms
    • WooCommerce Forms
    • WordPress Forms
    • Third-Party Form Plugins
    • Checkout Blocks Integration
    • Shortcode — Drop the Widget Anywhere
  • Protection & Monitoring

    • Analytics Dashboard
    • Rate Limiting — Auto-Lockout for Abusive IPs
    • Recovery URL — Unlock a Stuck IP
    • Email Digest
    • Webhooks — Real-Time Alerts on Bot Spikes
  • Developer

    • REST API
    • Filters & Hooks
    • Site Health Integration
  • Compare

    • Turnstile vs Google reCAPTCHA
    • Turnstile vs hCaptcha
  • Help

    • Troubleshooting
    • Frequently Asked Questions
    • Glossary

REST API

The plugin ships a small REST API under the namespace /wkcft/v1. Use it for:

  • Verifying Turnstile tokens from a headless / SPA frontend
  • Reading analytics programmatically
  • Reading or writing conditional rules and Design Studio settings

Base URL

https://yoursite.com/wp-json/wkcft/v1

Authentication

Two auth tiers:

EndpointAuthRate Limit
POST /verifyPublic10 requests / 60 sec per IP
GET /statsmanage_woocommerce capability—
GET /conditionsmanage_woocommerce—
POST /conditionsmanage_woocommerce—
GET /design-studiomanage_woocommerce—
POST /design-studiomanage_woocommerce—

Protected endpoints use standard WordPress REST authentication — Application Passwords, cookie auth for admin UI, OAuth via a plugin like JWT Auth.

POST /verify — Verify a Token

Use this from your headless frontend or SPA to verify a Turnstile token on your own server-to-server.

Request

POST /wp-json/wkcft/v1/verify
Content-Type: application/json

{
  "token": "0x4AAA..._LONG_CLOUDFLARE_TOKEN_..."
}

Optional fields:

  • site_key — Override the stored site key (useful for multi-tenant SaaS)
  • secret_key — Override the stored secret key

Response

{
  "success": true,
  "error-codes": []
}

Or on failure:

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

Rate Limit

  • Window: 60 seconds
  • Limit: 10 requests per IP
  • Exceeded: HTTP 429

Example with fetch()

const response = await fetch('/wp-json/wkcft/v1/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ token: turnstileToken })
});

const result = await response.json();

if (result.success) {
  // Proceed with form submission
} else {
  alert('CAPTCHA failed: ' + result['error-codes'].join(', '));
}

Example with curl

curl -X POST https://yoursite.com/wp-json/wkcft/v1/verify \
  -H "Content-Type: application/json" \
  -d '{"token": "YOUR_TOKEN"}'

GET /stats — Analytics Data

Same data as the Analytics Dashboard, in JSON.

Request

GET /wp-json/wkcft/v1/stats?range=30

Query params:

  • range (optional) — days to include. Must be one of 7, 30, or 90. Default 30. Any other value returns HTTP 400 with code rest_invalid_param.

Response

{
  "summary": {
    "total": 12500,
    "passed": 11200,
    "failed": 1300,
    "pass_rate": 89.6
  },
  "top_forms": [
    { "form": "checkout", "total": 5000, "passed": 4700, "failed": 300, "pass_rate": 94.0 },
    { "form": "login",    "total": 3000, "passed": 2800, "failed": 200, "pass_rate": 93.3 }
  ],
  "top_errors": {
    "invalid-input-response": 800,
    "timeout-or-duplicate":   500
  },
  "range": 30
}
  • summary — 4 counters, aggregated across the window.
  • top_forms — top 10 forms by volume, each with per-form pass rate.
  • top_errors — map of Cloudflare error code → occurrences.
  • range — echo of the resolved window.

Example with WP REST nonce

const response = await fetch('/wp-json/wkcft/v1/stats?range=7', {
  headers: { 'X-WP-Nonce': wpApiSettings.nonce }
});
const stats = await response.json();
console.log(stats.summary.pass_rate);

Example with Application Password

curl https://yoursite.com/wp-json/wkcft/v1/stats?range=7 \
  -u "admin:xxxx xxxx xxxx xxxx xxxx xxxx"

GET /conditions — Read Conditions

Returns the current conditional rules config.

Request

GET /wp-json/wkcft/v1/conditions

Response

{
  "skip_logged_in": "no",
  "skip_known_customers": "no",
  "require_after_failures": 0,
  "ip_whitelist": [],
  "ip_blacklist": [],
  "country_whitelist": [],
  "country_blacklist": []
}

Field reference:

FieldTypeNotes
skip_logged_in"yes" | "no"Bypass Turnstile for any logged-in user.
skip_known_customers"yes" | "no"Bypass for customers with at least one completed order.
require_after_failuresint (0–100)Only require CAPTCHA after N failed login attempts per IP. 0 = always require.
ip_whiteliststring[]IPv4/IPv6 addresses or CIDR ranges to bypass.
ip_blackliststring[]IPs/CIDR to always require.
country_whiteliststring[]Two-letter ISO country codes to bypass.
country_blackliststring[]Two-letter ISO codes to always require.

POST /conditions — Write Conditions

Update the conditional rules config.

Request

POST /wp-json/wkcft/v1/conditions
Content-Type: application/json

{
  "config": {
    "skip_logged_in": "yes",
    "require_after_failures": 3,
    "ip_whitelist": ["10.0.0.0/8", "203.0.113.42"]
  }
}

Fields missing from the config object are dropped to their sanitized defaults — send the full object if you want a partial merge to be persisted as-is.

Response

Returns the sanitized, persisted config directly (not wrapped):

{
  "skip_logged_in": "yes",
  "skip_known_customers": "no",
  "require_after_failures": 3,
  "ip_whitelist": ["10.0.0.0/8", "203.0.113.42"],
  "ip_blacklist": [],
  "country_whitelist": [],
  "country_blacklist": []
}

Error when config is not a JSON object:

{
  "code": "rest_invalid_param",
  "message": "config must be a JSON object.",
  "data": { "status": 400 }
}

GET /design-studio — Read Design Config

Returns the current Design Studio array.

Request

GET /wp-json/wkcft/v1/design-studio

Response

{
  "theme": "",
  "size": "",
  "language": "",
  "alignment": "",
  "margin_top": 0,
  "margin_bot": 0,
  "padding": 0,
  "bg_color": "",
  "border_color": "",
  "border_w": 0,
  "border_rad": 0,
  "shadow": "none",
  "label_text": "",
  "label_color": "#1d2327",
  "helper_text": "",
  "helper_color": "#646970"
}

Field reference:

FieldTypeAllowed values
themeenum"" (default) · light · dark · auto
sizeenum"" · normal · compact · flexible
languagestringempty or ISO code matching /^[a-zA-Z-]{2,8}$/
alignmentenum"" · left · center · right
margin_top, margin_botint0–100 (px)
paddingint0–40 (px)
bg_color, border_colorhex stringempty = transparent
border_wint0–8 (px)
border_radint0–32 (px)
shadowenumnone · subtle · medium · strong
label_textstring≤ 120 chars
helper_textstring≤ 200 chars
label_color, helper_colorhex stringCSS colors

POST /design-studio — Write Design Config

Update Design Studio settings.

Request

POST /wp-json/wkcft/v1/design-studio
Content-Type: application/json

{
  "config": {
    "theme": "dark",
    "shadow": "medium"
  }
}

Fields missing from the config object reset to their defaults — send the full object to guarantee untouched keys stay.

Response

Returns the sanitized, persisted config directly (not wrapped):

{
  "theme": "dark",
  "size": "",
  "language": "",
  "alignment": "",
  "margin_top": 0,
  "margin_bot": 0,
  "padding": 0,
  "bg_color": "",
  "border_color": "",
  "border_w": 0,
  "border_rad": 0,
  "shadow": "medium",
  "label_text": "",
  "label_color": "#1d2327",
  "helper_text": "",
  "helper_color": "#646970"
}

Common Patterns

Programmatic Multi-Site Rollout

Push the same Design Studio config to a fleet of stores via a shell script:

for SITE in store1.com store2.com store3.com; do
  curl -X POST "https://$SITE/wp-json/wkcft/v1/design-studio" \
    -u "admin:xxx" \
    -H "Content-Type: application/json" \
    -d '{"config":{"theme":"dark","label_text":"Secure"}}'
done

SPA Form Example (React)

import { Turnstile } from '@marsidev/react-turnstile';

function CheckoutForm() {
  const [token, setToken] = useState('');
  
  async function handleSubmit(e) {
    e.preventDefault();
    
    const verify = await fetch('/wp-json/wkcft/v1/verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token })
    });
    
    const result = await verify.json();
    if (!result.success) {
      alert('CAPTCHA failed');
      return;
    }
    
    // Proceed with your order submission
    await placeOrder(formData);
  }
  
  return (
    <form onSubmit={handleSubmit}>
      ...
      <Turnstile siteKey="YOUR_SITE_KEY" onSuccess={setToken} />
      <button type="submit">Place Order</button>
    </form>
  );
}

Custom REST Verification Hook

Register your own endpoint that internally calls the validator:

add_action('rest_api_init', function() {
    register_rest_route('my-api/v1', '/submit-form', [
        'methods' => 'POST',
        'callback' => function($request) {
            $token = $request->get_param('cf-turnstile-response');
            $result = WKCFT_Validator::wkcft_instance()->wkcft_check($token, 'my-custom-form');
            
            if (!$result['success']) {
                return new WP_Error('captcha_failed', 'CAPTCHA failed', ['status' => 400]);
            }
            
            // Process your form
            return ['success' => true];
        },
        'permission_callback' => '__return_true'
    ]);
});

Error Codes

Common REST errors:

CodeHTTPMeaning
rest_forbidden401Not authenticated
rest_cannot_view403Authenticated but lacks capability
wkcft_missing_site_key400No site key on the request and none stored in settings
wkcft_missing_secret_key400No secret key on the request and none stored in settings
rest_invalid_param400config not a JSON object, or range not in [7, 30, 90]
wkcft_rate_limited429/verify hit the 10-per-60s per-IP rolling limit
wkcft_class_unavailable503Required module (Logger / Conditions / Design Studio) not loaded on the host

Extensibility

Add your own routes under the same namespace:

add_action('rest_api_init', function() {
    register_rest_route('wkcft/v1', '/my-extension', [
        'methods' => 'GET',
        'callback' => 'my_extension_callback',
        'permission_callback' => function() {
            return current_user_can('manage_woocommerce');
        }
    ]);
});

Troubleshooting

ProblemFix
404 on /verifyPermalinks may be flat. Go to Settings → Permalinks → Save
401 on /statsSend X-WP-Nonce header (from wp_rest nonce) or use Application Password
403 after authYour account lacks manage_woocommerce capability
/verify returns success: false with no error-codesSecret Key is missing — set on API Settings tab
Rate-limited on /verifyIncrease the limit via filter wkcft_rate_limit_threshold (if your use case is legitimate)

Related Pages

  • Filters & Hooks — Customize validation and limits
  • Shortcode — Alternative to REST for custom forms
  • Conditional Rules — Backing data for /conditions
  • Design Studio — Backing data for /design-studio
Next
Filters & Hooks