<- blog

A Turnstile Pass Is Not a Qualified Lead

Cloudflare's new Turnstile setup flow exposes a useful rule: protect availability, request integrity, and business acceptance separately.

#security#forms#reliability

Cloudflare made Turnstile Spin generally available on 10 August. It can create a widget from the dashboard, Wrangler, or an AI coding agent, then guide the integration of the frontend token and backend siteverify call. Its most valuable behaviour is not code generation: before finishing, the agent can send a real token through the protected endpoint, confirm that it passes once, and replay it to confirm that the endpoint rejects the duplicate.

One day later, Cloudflare's H1 2026 DDoS report described a very different traffic problem. It recorded 935 network-layer attacks above 1 Tbps in the first half of the year, while 90.60% of observed network-layer attacks ended in under ten minutes. Those bursts are too fast for a person to notice, diagnose, and mitigate during the attack.

The two releases belong in the same form-security conversation, but they do not solve the same problem. DDoS protection preserves availability under hostile traffic. Turnstile can prove that a fresh, valid challenge token accompanied one request. Neither proves that a quote, booking, account, checkout, or contact submission is commercially valid.

The repeated angle to avoid

The ten most recent posts here covered agent conversion tracking, agent ROI, prototype exits, Copilot billing, code-quality setup, comment-triggered agents, AI gateway budgets, model entitlements, worktree isolation, and stacked pull requests. Older overlapping posts covered bot-traffic analytics, server-side conversion outcomes, edge-controlled uploads, security-scan pipelines, form UX, credentials, and recovery workflows.

The weak version would repeat the old X needs Y formula: lead forms need bot protection. The sharper thesis is that form defence fails when several different states are compressed into one label such as verified, success, or conversion. Availability, request integrity, payload validity, side-effect completion, and lead quality need separate evidence because each can succeed while the next one fails.

Fresh evidence and background context

The source map separates current product evidence from durable implementation guidance:

Source Freshness What it contributes
Turnstile Spin GA 10 August 2026 Three setup paths, mandatory backend wiring, a real-token acceptance check, and a replay-rejection check
Cloudflare H1 DDoS report 11 August 2026 Evidence that availability attacks can be both extremely large and too short for manual mitigation
Cloudflare analysis of good, bad, and hybrid automated behaviour 7 August 2026 Evidence that traffic can shift between human and agent behaviour and that a one-time check lacks whole-session context
Turnstile server-side validation docs Background, updated 5 May 2026 Token lifetime, single-use semantics, response fields, and validation patterns
OWASP input validation guidance Background The distinction between syntactic and business-context semantic validation

Cloudflare's numbers describe traffic on its own network, not every site. They still expose an implementation boundary that applies more broadly: one control should not be credited for a different control's job.

Three boundaries protect three different assets

A practical intake flow has at least three boundaries.

Boundary Asset being protected Useful controls Positive result actually proves
Availability DNS, network, edge, origin capacity, and dependencies Always-on DDoS protection, caching, rate limits, connection limits, queues The service stayed able to receive or degrade requests
Request integrity The form or API endpoint Turnstile, server-side siteverify, expected hostname and action, token replay rejection This request carried a token the verifier accepted for the intended surface
Business acceptance Staff time, CRM quality, inventory, bookings, email, payment, and analytics Server schema checks, semantic rules, duplicate controls, authorization, spam scoring, idempotency, downstream reconciliation The payload met the rules for a specific business transition

This separation prevents two dangerous conclusions.

First, a site that survives a traffic burst is not necessarily rejecting abusive form submissions. Network capacity and application-level intent are different problems. Second, a form that validates a Turnstile token is not necessarily receiving a useful customer record. A valid requester can still send nonsense, submit the same job repeatedly, select an unsupported service area, exhaust an expensive quote calculation, or create a downstream side effect twice.

Cloudflare's fresh analysis of hybrid human and agent behaviour makes the distinction more important. A real customer may delegate part of a shopping or booking journey to an agent. Blocking everything automated can reject useful demand; trusting everything that passed one browser check can admit abuse. Request proof should therefore stay separate from business authority and value.

What siteverify should gate

Cloudflare's server-side validation documentation is explicit: the client widget alone does not protect the endpoint. A caller can bypass the page and send any string directly to the handler. The server has to send the token to siteverify and make its own decision from the response.

The token properties create a narrow security contract:

  • it expires after 300 seconds;
  • it is single-use;
  • a replay returns timeout-or-duplicate;
  • the response can include hostname, action, challenge time, and error codes;
  • an idempotency key can make retries of the verification request safer.

For a request_quote form, do not stop at success === true. Check that the returned action is request_quote and that the hostname belongs to the expected production set. Keep the secret on the server. Put a timeout around the external verification call. Decide whether a verifier outage fails closed, opens a reduced path, or queues a recoverable submission.

That last choice is a business trade-off. Failing closed protects a high-risk account or payment action but can lose ordinary enquiries during a provider incident. Failing open preserves leads but removes the request-integrity gate exactly when its status is unknown. A useful middle path for low-risk contact forms is to accept into a quarantined state, suppress expensive side effects, and require later verification or staff review.

The form should be a state machine, not one success boolean

Use explicit states so analytics and operations cannot confuse proof with outcome:

request_received
  -> availability_admitted
  -> request_proof_passed | request_proof_failed | proof_unavailable
  -> payload_valid | payload_invalid
  -> duplicate_rejected | business_accepted
  -> downstream_completed | downstream_failed | manual_review
  -> lead_qualified | lead_unqualified

Only the final state answers whether the submission became a qualified lead. For ecommerce, the equivalent final state may be order_paid or order_fulfillable. For bookings, it may be booking_confirmed. For account creation, it may require verified contact details and abuse checks after the initial request.

This state model also improves conversion reporting. request_proof_passed is a security event. business_accepted is an application event. downstream_completed is an integration event. lead_qualified is a commercial event. Giving them different names prevents a dashboard from counting a Turnstile pass, an HTTP 200 response, and a CRM record as three conversions.

A small server-side implementation pattern

The exact framework is less important than the order. Perform cheap bounded checks early, verify request proof before expensive work, validate business semantics before side effects, and make the accepted operation idempotent.

async function submitQuote(request: Request) {
  const body = await readBoundedFormData(request, { maxBytes: 32_000 });

  const syntax = validateQuoteSchema(body);
  if (!syntax.ok) return reject("payload_invalid", 400);

  const proof = await verifyTurnstile({
    token: body.turnstileToken,
    expectedAction: "request_quote",
    allowedHostnames: ["example.com", "www.example.com"],
    timeoutMs: 3_000,
  });
  if (!proof.ok) return reject(proof.reason, 403);

  const semantics = await validateQuoteRules({
    service: body.service,
    postcode: body.postcode,
    requestedDate: body.requestedDate,
  });
  if (!semantics.ok) return reject("business_invalid", 422);

  const operationKey = stableHash([
    normaliseEmail(body.email),
    body.service,
    body.postcode,
    timeBucket("15m"),
  ]);

  const lead = await createLeadOnce(operationKey, body);
  await enqueueNotificationsOnce(lead.id);

  return accepted({ leadId: lead.id, state: "business_accepted" });
}

A real handler also needs safe logging, privacy limits, CSRF and authentication where appropriate, output encoding, dependency timeouts, and rate policy. The pattern's main point is ordering: a browser token should not replace schema validation, semantic rules, duplicate suppression, or downstream idempotency.

Test the rejected paths, not only the visible widget

Spin's real-token replay test is a strong minimum because it proves that the protected endpoint—not merely the page—enforces single-use verification. Extend that idea into an acceptance matrix.

Test Expected state Business side effect
Valid token, expected action and hostname, valid quote business_accepted One CRM lead and one notification job
Missing or forged token sent directly to the endpoint request_proof_failed None
Valid token replayed request_proof_failed with duplicate reason None
Valid token from the wrong action or hostname request_proof_failed None
Expired token after a long form completion Recoverable proof error with widget reset None until resubmitted
Valid token with malformed payload payload_invalid None
Valid token with unsupported service area business_invalid None, or an explicit referral path
Two valid requests for the same operation One accepted, one duplicate_rejected Exactly one downstream record
siteverify timeout Documented fail-closed or quarantined path No unreviewed email, CRM, payment, or AI work
CRM accepts then notification provider fails downstream_failed with retryable step Lead preserved; notification retried once per job key

Cloudflare publishes dummy sitekeys and secret keys for automated Turnstile testing, including predictable pass, fail, interactive, and already-spent behaviours. Keep those keys outside production, and pair them correctly: production secrets reject dummy tokens.

Failure modes that a polished widget can hide

The widget exists, but the endpoint never verifies

This is the exact integration gap Spin is designed to surface. An attacker skips the page and calls the endpoint directly. Dashboard evidence of widgets without matching siteverify traffic should be treated as an incomplete deployment, not a cosmetic warning.

Verification succeeds, but action context is ignored

One valid token is accepted on a different form or hostname than intended. Check the response context against server-owned expectations rather than trusting client-submitted labels.

Proof is replay-safe, but business work is not

A customer double-clicks, an agent retries after a timeout, or two fresh tokens carry the same quote request. Turnstile's single-use rule protects its token, not your CRM insert, email send, stock reservation, or payment call. Those operations need their own idempotency keys.

The edge is healthy while dependencies collapse

A short traffic burst can leave the public page reachable while the CRM, email provider, postcode lookup, pricing service, or database pool becomes saturated. Apply rate and concurrency limits to expensive downstream work, then queue or degrade where the customer journey permits it.

Strict bot policy blocks delegated customers

A customer-authorized agent may not behave like a mouse-driven visitor. Keep a deterministic accessible form path, publish machine-readable information where useful, and evaluate the submitted action rather than treating automation as automatic fraud.

Every rejection becomes invisible

If all failures return one generic error and leave no safe server event, operators cannot distinguish attacks from expired tokens, broken deployments, business-rule changes, or provider outages. Record a bounded reason code and correlation ID, but do not put secrets, full messages, or unnecessary personal data into general security logs.

The practical conclusion

Turnstile Spin reduces an important class of setup error because it does not stop when a widget renders. It follows the proof into the backend and exercises the duplicate path. The larger operator lesson is to continue that discipline through the rest of the funnel.

Keep availability evidence at the edge. Keep request-proof evidence at the verifier. Keep payload and semantic decisions in the application. Keep side-effect completion in the system that performed the work. Keep qualification in the CRM or operating process that knows whether the lead was useful.

Then one passing token can mean exactly what it should mean: this request crossed one integrity boundary. It will not be mistaken for a protected origin, a valid quote, a completed booking, a paid order, or a qualified customer.

Need technical help?

I'm a software engineer who builds web apps, APIs, and AI tooling. If you've got a project or a problem to talk through, book a free 30-minute call.

Book time with me ->