Cloudflare Turnstile
Cloudflare Turnstile is a CAPTCHA alternative that provides user verification.
Schema Integration
Turnstile integrates with the existing GraphQL schema through the CaptchaType
enum:
enum CaptchaType { TURNSTILE # Cloudflare Turnstile HCAPTCHA # hCaptcha V2_VISIBLE # reCAPTCHA v2 with visible checkbox V2_INVISIBLE # reCAPTCHA v2 without visible elements}
type CaptchaConfiguration { type: CaptchaType! siteKey: String!}
Implementation Examples
Successful Login with Turnstile
When a user completes Turnstile verification, the token can bypass rate limiting:
mutation Login($input: LoginInput!) { login(input: $input) { success token customer { id email fullName } error fieldErrors { fieldName validators requiredButNotProvided } }}
Request Headers:
X-Captcha-Type: TURNSTILEX-Captcha-Response: 0.ABC123DEF456GHI789JKL012MNO345PQR678STU901VWX234YZ567Content-Type: application/json
Rate Limited Response with Turnstile Configuration
When rate limiting is triggered, Turnstile configuration is provided:
{ "data": { "login": null }, "errors": [ { "message": "Rate limit exceeded", "path": ["login"], "extensions": { "classification": "CAPTCHA_REQUIRED", "rateLimited": true, "rateLimitingBucket": "AUTHENTICATION" } } ], "extensions": { "rateLimitersFiring": [ { "captchaBypassAvailable": [ { "type": "TURNSTILE", "siteKey": "0x4AAAAAAAA1234567890ABCDEF" } ], "rateLimitingBucket": "AUTHENTICATION" } ] }}
Error Handling
Common Turnstile error responses:
Error Code | Description |
---|---|
missing-input-response | No token provided |
invalid-input-response | Invalid or expired token |
timeout-or-duplicate | Token reuse or timeout |
bad-request | Malformed request |
sitekey-secret-mismatch | Configuration error |
API Usage
Both web and mobile APIs support Turnstile using the same header pattern:
// Web APIfetch('https://horizon-api.www.myprotein.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Captcha-Type': 'TURNSTILE', 'X-Captcha-Response': turnstileToken }, body: JSON.stringify({ query, variables })});
// Mobile APIfetch('https://api.thehut.net/myprotein/en/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Captcha-Type': 'TURNSTILE', 'X-Captcha-Response': turnstileToken, 'Authorization': 'Opaque ' + authToken }, body: JSON.stringify({ query, variables })});