Turnstile meets the WAF: clearing fetch requests without breaking the UI

Cloudflare Turnstile has been generally available for two months, giving site owners a CAPTCHA-free way to distinguish humans from bots. The original implementation protected page views, but browser-initiated API calls used by React, Angular, and Vue.js apps presented a gap: a challenge served in response to a fetch() request arrives as HTML, which the browser cannot render in place of the expected JSON or XML. That left administrators with little choice but to block suspicious traffic outright, risking false positives on legitimate users.

Turnstile is now integrated with the Cloudflare Web Application Firewall (WAF), allowing those same challenge decisions to be managed at the edge. Administrators can embed the Turnstile snippet and then use WAF Rules to decide what happens to cleared visitors — for example, letting them through to all API endpoints, or forcing a fresh challenge on sensitive routes like login.

Pre-clearing visitors before they hit the API

Cloudflare's WAF has long used JS Challenge, Managed Challenge, and Interactive Challenge to screen requests. Successful completion issues a cf_clearance cookie tied to the individual visitor, the challenge type, and the time it was solved. The cookie cannot be shared between users and expires according to the zone's “Challenge Passage” setting. The limitation: browsers cannot execute the challenge HTML received on a fetch or XHR request if the visitor hasn't already passed a challenge. Turnstile addresses this by issuing a clearance cookie for the domain where it is embedded, not just a one-time token. A challenge rendered inside a normal HTML page can thus pre-clear the visitor before they make any API calls.

Consider an online ordering form built in React. A visitor can pass a Managed Challenge while viewing the payment page, but the subsequent fetch() submitting credit card details would otherwise receive a challenge response it cannot render. With Turnstile Pre-Clearance, the challenge runs in the background while the visitor fills in their details, and the payment request reaches the API without interruption.

Advantages of Pre-Clearance mode

  1. Better user experience: the embedded challenge executes while the visitor is already engaged with the form.
  2. More requests blocked at the edge: since Turnstile now sets a clearance cookie for the embedding domain, a Custom Rule can issue a Managed Challenge on every request to a sensitive API endpoint. Direct automated attacks are stopped before they reach the application.
  3. Optional per-request validation: no backend changes are needed to benefit from Pre-Clearance, but validating the Turnstile token server-side on each submission adds protection against session hijacking for critical endpoints.

Pre-Clearance widgets still issue Turnstile tokens, so an administrator can choose whether to require a security check on every request to a given endpoint or just once per session. The clearance cookie is automatically applied to the Cloudflare zone where the widget is embedded, with no extra configuration.

Implementing Pre-Clearance in a single-page app

A basic implementation can be demonstrated with a frontend that talks to a backend at /your-api. The initial code creates a button that issues a fetch() call to that endpoint and renders the result in a response container:

<!DOCTYPE html>
<html lang="en">
<head>
   <title>Turnstile Pre-Clearance Demo </title>
</head>
<body>
  <main class="pre-clearance-demo">
    <h2>Pre-clearance Demo</h2>
    <button id="fetchBtn">Fetch Data</button>
    <div id="response"></div>
</main>

<script>
  const button = document.getElementById('fetchBtn');
  const responseDiv = document.getElementById('response');
  button.addEventListener('click', async () => {
  try {
    let result = await fetch('/your-api');
    if (result.ok) {
      let data = await result.json();
      responseDiv.textContent = JSON.stringify(data);
    } else {
      responseDiv.textContent = 'Error fetching data';
    }
  } catch (error) {
    responseDiv.textContent = 'Network error';
  }
});
</script>

If a WAF rule protects /your-api with a Managed Challenge, this straightforward application breaks:

BLOG-2134 Embedded Image - dsgsaR

Inspecting the Network Tab shows the request received a 403 response:

BLOG-2134 Embedded Image - dDljfa

The Cf-Mitigated header reveals the response was challenged by Cloudflare's firewall because the visitor has not yet solved a challenge:

BLOG-2134 Embedded Image - 3AFE7x

To fix this, set up the Turnstile widget in Pre-Clearance mode for the sitekey used by the application:

BLOG-2134 Embedded Image - upnapb

In the application code, override the fetch() function to trigger Turnstile whenever a Cf-Mitigated response is received:

BLOG-2134 Embedded Image - 5mhV7x

The overriding code performs several steps. It creates a hidden overlay element and replaces the browser's fetch() function with one that inspects the Cf-Mitigated header for the value challenge. If a challenge is detected, the initial request is treated as unsuccessful, and a Turnstile overlay with Pre-Clearance enabled appears in the application. After the Turnstile challenge is solved and the cf_clearance cookie is set, the original request is retried and passes through the WAF.

Once the widget is solved, the overlay disappears and the API response displays successfully:

BLOG-2134 Embedded Image - R9HydL

Availability

Turnstile Pre-Clearance is available to all Cloudflare customers, including those on the free plan, for an unlimited number of requests in managed mode. Interested users can create a widget with Pre-Clearance directly from the Cloudflare dashboard.