<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peak Fo</title>
    <description>The latest articles on DEV Community by Peak Fo (@peakfodev).</description>
    <link>https://dev.to/peakfodev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4114565%2Faab91118-b4bb-459c-a84f-4fdd710840d2.png</url>
      <title>DEV Community: Peak Fo</title>
      <link>https://dev.to/peakfodev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peakfodev"/>
    <language>en</language>
    <item>
      <title>How to Scrape Cloudflare-Protected Sites Without Getting Blocked</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Fri, 09 Oct 2026 07:17:53 +0000</pubDate>
      <link>https://dev.to/peakfodev/how-to-scrape-cloudflare-protected-sites-without-getting-blocked-3ane</link>
      <guid>https://dev.to/peakfodev/how-to-scrape-cloudflare-protected-sites-without-getting-blocked-3ane</guid>
      <description>&lt;p&gt;Cross-posted from &lt;a href="https://blog.peak.fo/how-to-scrape-cloudflare-protected-sites-without-getting-blocked/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on &lt;a href="https://peak.fo/?ref=blog.peak.fo&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=scrape-cloudflare-sites" rel="noopener noreferrer"&gt;Peak&lt;/a&gt;, a Cloudflare Turnstile/5-second-challenge solving API, so the code example below calls it — the proxy, header, and pacing advice stands regardless of which solver you use.&lt;/p&gt;




&lt;p&gt;You write a scraper, it works on your machine, and the moment you point it at a Cloudflare site you get a wall: a challenge page, a 403, or an endless "checking your browser." Cloudflare sits in front of a huge share of the web now, so if you scrape at all, you'll meet it. Here's how to collect public data from Cloudflare-protected sites without tripping every alarm, and how to stay on the right side of the line while you do it.&lt;/p&gt;

&lt;p&gt;First, the honest framing. This is about legitimate work: public data, price monitoring, research, testing your own properties. Respect the target's terms of service and its robots rules, keep your request rate reasonable, and don't hammer infrastructure you don't own. The techniques below reduce false-positive blocks on legitimate crawling; they aren't a license to abuse a service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cloudflare blocks you
&lt;/h2&gt;

&lt;p&gt;Cloudflare stacks several defenses, and you can hit any of them. IP reputation comes first: a datacenter IP that thousands of scrapers already burned is suspect before you send a single header. Then there's the environment check, where JavaScript challenges like &lt;a href="https://blog.peak.fo/what-is-cloudflare-turnstile-how-it-works/" rel="noopener noreferrer"&gt;Turnstile&lt;/a&gt; and the 5-second interstitial look at whether a real browser is present. On top of that, rate and pattern analysis flags traffic that behaves like a script: too fast, too regular, no human rhythm.&lt;/p&gt;

&lt;p&gt;Get blocked and it's usually one of those three, not some unbeatable magic. Fix them in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  The playbook
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Use good proxies, and keep the session sticky
&lt;/h3&gt;

&lt;p&gt;This is the biggest lever. Residential and mobile IPs carry far better reputation than datacenter ranges, because they look like real users. Rotate them so you're not slamming a site from one address, but keep a session &lt;em&gt;sticky&lt;/em&gt; when you need continuity: if you've earned a clearance cookie, the follow-up requests have to come from the same IP or Cloudflare throws the cookie out. Cheap datacenter proxies are a false economy here; they're where most "why am I blocked" stories start.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Send believable headers
&lt;/h3&gt;

&lt;p&gt;A default Python or Node user-agent is a giveaway. Send a real browser user-agent, and make the rest of your headers consistent with it (accept-language, accept-encoding, the usual set). The point isn't to lie; it's to not stick out as an obviously scripted client when you're doing ordinary crawling.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Clear the challenges, then reuse what you get
&lt;/h3&gt;

&lt;p&gt;When you hit an actual challenge, you have two things to handle. The &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;Turnstile widget&lt;/a&gt; returns a token you submit with the form. The 5-second interstitial returns a &lt;a href="https://blog.peak.fo/the-cf_clearance-cookie-explained-and-how-to-reuse-it/" rel="noopener noreferrer"&gt;cf_clearance cookie&lt;/a&gt; you reuse on later requests. Solve once, then reuse: don't re-challenge yourself on every request. With a solving API this is a single call that hands back the token or the cookie plus the matching user-agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# clear the 5s challenge once, keep the clearance for the session
&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cloudflare5stask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@ip:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cookies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# now crawl with s, same proxy, at a human pace
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Slow down and vary
&lt;/h3&gt;

&lt;p&gt;Machine-perfect timing is a tell. Space requests out, add jitter, and don't crawl a thousand pages a minute from one session. Respect the rate the site can reasonably serve. Slower and steady beats fast and blocked, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser or API for the challenge?
&lt;/h2&gt;

&lt;p&gt;If your crawl needs to click through pages and read content that only renders after interaction, run a real browser and let it handle the challenge inline. If you just need the token or the cookie and then you're making plain HTTP requests, an API is lighter and scales without a browser per worker. Most large crawls end up using the API for the challenge and their own HTTP client for the actual fetching. We break the trade-off down in the &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;Turnstile solving guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I scrape a Cloudflare-protected site?&lt;/strong&gt;&lt;br&gt;
Use residential or mobile proxies with sticky sessions, send real browser headers, solve any Turnstile or 5-second challenge and reuse the token or cf_clearance cookie, and crawl at a human pace. Most blocks come down to a bad IP, a scripted-looking client, or too-fast requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is scraping Cloudflare sites legal?&lt;/strong&gt;&lt;br&gt;
Scraping public data is generally fine, but it depends on the site's terms of service, its robots rules, and what you do with the data. Don't access private or account-gated content you're not authorized to, and respect rate limits. The tooling is neutral; the use is on you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do I keep getting the 5-second challenge?&lt;/strong&gt;&lt;br&gt;
Usually because you're not reusing the cf_clearance cookie, or you're sending it from a different IP or user-agent than the one that earned it. Keep the session sticky and send the matching user-agent. See the &lt;a href="https://blog.peak.fo/the-cf_clearance-cookie-explained-and-how-to-reuse-it/" rel="noopener noreferrer"&gt;cf_clearance guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://peak.fo/?ref=blog.peak.fo&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=scrape-cloudflare-sites" rel="noopener noreferrer"&gt;Start free at peak.fo&lt;/a&gt;. Bring your own proxy, pay only for solves that land.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>cloudflare</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>CAPTCHA Solving API vs Headless Browser: Which to Use</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Thu, 08 Oct 2026 07:24:38 +0000</pubDate>
      <link>https://dev.to/peakfodev/captcha-solving-api-vs-headless-browser-which-to-use-4bcb</link>
      <guid>https://dev.to/peakfodev/captcha-solving-api-vs-headless-browser-which-to-use-4bcb</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/captcha-solving-api-vs-headless-browser-which-to-use/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, the solving API mentioned below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two teams hit the same Cloudflare wall. One spins up a fleet of headless Chrome instances with stealth plugins. The other sends an HTTP POST to a solving API and gets a token back. Both work. But by month three they're in very different places, and it's usually not the place they expected.&lt;/p&gt;

&lt;p&gt;Here's the short version. &lt;strong&gt;Use a solving API when you need the token or the clearance cookie and nothing else. Use a headless browser when you need to stay on the page and interact with it&lt;/strong&gt; after the challenge clears. Most scraping and automation jobs are the first kind, which is why the API route wins more often than people assume going in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real trade-off, dimension by dimension
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Solving API&lt;/th&gt;
&lt;th&gt;Headless browser&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What you get back&lt;/td&gt;
&lt;td&gt;A token or &lt;code&gt;cf_clearance&lt;/code&gt; cookie&lt;/td&gt;
&lt;td&gt;A live browser you keep driving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed per solve&lt;/td&gt;
&lt;td&gt;~1 to 1.5s on a typical target&lt;/td&gt;
&lt;td&gt;Seconds, plus browser startup and page load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Pay per successful solve&lt;/td&gt;
&lt;td&gt;Your CPU, RAM, and proxy bandwidth, always&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;The vendor chases Cloudflare changes&lt;/td&gt;
&lt;td&gt;You patch stealth every time detection shifts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale&lt;/td&gt;
&lt;td&gt;Concurrency is just more requests&lt;/td&gt;
&lt;td&gt;Each browser is hundreds of MB of RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best when&lt;/td&gt;
&lt;td&gt;You only need to get past the check&lt;/td&gt;
&lt;td&gt;You need full JS state and page interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where the headless browser genuinely wins
&lt;/h2&gt;

&lt;p&gt;Don't let anyone tell you the browser is always the wrong answer. If your job is to log in, navigate three pages deep, click through a JavaScript-heavy flow, and read state that only exists in a live DOM, you need a browser anyway. Once you're paying for that browser, solving the challenge inside it can make sense, because the page context, cookies, and fingerprint are already consistent.&lt;/p&gt;

&lt;p&gt;The catch is what it costs you to keep that browser undetected. Cloudflare's bot management reads canvas fingerprints, WebGL, timing, and dozens of other signals. Stealth patches drift out of date. You end up maintaining an anti-detection layer as a permanent side project, and every Cloudflare update is a fire drill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the API wins
&lt;/h2&gt;

&lt;p&gt;If all you actually need is the &lt;code&gt;cf-turnstile-response&lt;/code&gt; token to submit with a form, or the &lt;code&gt;cf_clearance&lt;/code&gt; cookie to reuse on requests, a browser is a lot of machinery for a small output. The API turns the whole thing into one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turnstiletask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAAAxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@ip:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# submit token with your request; done
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No browser pool, no stealth plugins, no RAM ceiling on concurrency. When Cloudflare changes something, that's the vendor's problem to fix, not yours. And because Peak bills only on a successful solve, a failed attempt costs nothing, which quietly changes the math versus a browser that burns compute whether or not it gets through.&lt;/p&gt;

&lt;p&gt;If you want the token explained end to end, read &lt;a href="https://blog.peak.fo/the-cf-turnstile-response-token-explained/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=api-vs-headless" rel="noopener noreferrer"&gt;the cf-turnstile-response token, explained&lt;/a&gt;. For the cookie flow on the 5-second challenge, see &lt;a href="https://blog.peak.fo/the-cf_clearance-cookie-explained-and-how-to-reuse-it/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=api-vs-headless" rel="noopener noreferrer"&gt;the cf_clearance cookie&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick way to decide
&lt;/h2&gt;

&lt;p&gt;Ask one question: after the challenge clears, do you still need the browser? If yes, keep the browser and solve inside it. If no, and you just need the token or cookie to make your own requests, the API is less code, less infrastructure, and less maintenance. Plenty of teams run both, a browser for the deep interactive flows and the API for the high-volume "just get me past the gate" work.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a solving API faster than a headless browser?
&lt;/h3&gt;

&lt;p&gt;For getting a token, usually yes, because you skip browser startup and page render. A Peak solve typically returns in about one to one and a half seconds. A browser has to launch, load the page, and let the widget run before you can read the field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which is cheaper at scale?
&lt;/h3&gt;

&lt;p&gt;It depends on volume and what else you're doing. Browsers cost CPU, RAM, and proxy bandwidth continuously, including on failed attempts. A pay-per-solve API costs a fixed amount only when it succeeds. See &lt;a href="https://blog.peak.fo/how-to-choose-a-captcha-solving-service-2026/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=api-vs-headless" rel="noopener noreferrer"&gt;how to choose a solving service&lt;/a&gt; to run your own numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use both?
&lt;/h3&gt;

&lt;p&gt;Yes, and many teams do. Use the browser where you need live page interaction, and the API for high-volume token or cookie work. The &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;Turnstile solving guide&lt;/a&gt; walks through both paths.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Skip the browser pool. Grab a key free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=api-vs-headless" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. $0.90 per 1,000 successful Turnstile solves, down to $0.35 at volume, and failed solves aren't billed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>api</category>
      <category>automation</category>
    </item>
    <item>
      <title>reCAPTCHA v3 Enterprise: How Score-Based Solving Works (and Why There's No Puzzle)</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Wed, 30 Sep 2026 07:25:33 +0000</pubDate>
      <link>https://dev.to/peakfodev/recaptcha-v3-enterprise-how-score-based-solving-works-and-why-theres-no-puzzle-132j</link>
      <guid>https://dev.to/peakfodev/recaptcha-v3-enterprise-how-score-based-solving-works-and-why-theres-no-puzzle-132j</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from the &lt;a href="https://blog.peak.fo/recaptcha-v3-enterprise-how-score-based-solving-works/" rel="noopener noreferrer"&gt;Peak blog&lt;/a&gt;. I work on &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=recaptcha-v3-enterprise" rel="noopener noreferrer"&gt;Peak&lt;/a&gt;, an API for solving Cloudflare Turnstile and the Cloudflare 5-second challenge.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's no checkbox. No blurry traffic lights. You just submit a form and get quietly rejected, or dropped into a harder challenge, with no obvious CAPTCHA anywhere.&lt;/p&gt;

&lt;p&gt;That's reCAPTCHA v3, and it's a different animal from anything with a puzzle. It doesn't ask you to prove you're human. It watches, scores you, and lets the site decide what to do with the number.&lt;/p&gt;

&lt;p&gt;Understanding that score is the whole game, because you can't "solve" a v3 the way you solve a Turnstile widget. There's nothing to click. What you can do is understand what moves the score and what a solving service actually returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  How reCAPTCHA v3 works
&lt;/h2&gt;

&lt;p&gt;v3 runs invisibly on the page and, on an action you trigger (a submit, a login, a page view), produces a token carrying a score from 0.0 to 1.0. Higher means "looks human," lower means "looks like a bot." The site sends that token to Google's &lt;code&gt;siteverify&lt;/code&gt;, gets the score back, and applies its own threshold. A common setup blocks below 0.5, waves through above, and sometimes steps borderline traffic up to a harder challenge.&lt;/p&gt;

&lt;p&gt;The key difference from v2 or Turnstile: there is no pass/fail interaction. The token always issues. What varies is the number attached to it, and that number is computed from signals the site never shows you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Enterprise adds
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA Enterprise is the paid tier with more risk analysis behind the same idea. Instead of a bare score, it returns reason codes, richer signals, and tunable assessments the site can weigh however it likes. For you on the outside, it behaves like v3 but stricter and harder to fool, because there's more feeding the score than a single number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "solving" v3 is really "scoring high"
&lt;/h2&gt;

&lt;p&gt;Since the token always issues, getting one is trivial and useless; a token with a 0.1 score gets you blocked just the same. The real task is producing a token that scores high enough to clear the site's threshold. That depends on things a puzzle-solver can't brute force:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser reputation.&lt;/strong&gt; A clean, consistent, real-looking browser environment scores better than a flagged headless one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP reputation.&lt;/strong&gt; Residential and mobile IPs score higher than datacenter ranges Google associates with automation. This is exactly where &lt;a href="https://blog.peak.fo/residential-vs-datacenter-proxies-for-solving/" rel="noopener noreferrer"&gt;residential vs datacenter proxies&lt;/a&gt; starts to matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral consistency.&lt;/strong&gt; Actions that look like a real user's flow score better than a cold, mechanical hit on the endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account and history signals.&lt;/strong&gt; On Enterprise especially, prior behavior tied to the session feeds the assessment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why raising a v3 score is closer to looking legitimate than to cracking a challenge. A high-score token comes from a request that genuinely resembles a trusted user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Peak fits
&lt;/h2&gt;

&lt;p&gt;Today Peak covers Cloudflare Turnstile and the Cloudflare 5-second challenge, the two we've built and verified end to end. reCAPTCHA v3 Enterprise support is on the near-term roadmap, and because it's a scoring problem rather than a puzzle problem, the same fundamentals we already lean on carry over: a trustworthy browser environment and a good IP through your proxy. When it lands it'll follow the same shape as the rest of the API, so the switch is a task type, not a rewrite.&lt;/p&gt;

&lt;p&gt;Watch the &lt;a href="https://peak.fo/docs/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=recaptcha-v3-enterprise" rel="noopener noreferrer"&gt;Peak docs&lt;/a&gt; for the release, and if you're comparing what different challenges need, &lt;a href="https://blog.peak.fo/cloudflare-turnstile-vs-recaptcha-vs-hcaptcha-2026/" rel="noopener noreferrer"&gt;Turnstile vs reCAPTCHA vs hCaptcha&lt;/a&gt; lays out the landscape.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do right now
&lt;/h2&gt;

&lt;p&gt;If you're facing v3 today, the levers are the ones that lift the score: use residential or mobile IPs on strict targets, keep a clean and consistent browser environment, and don't hammer the endpoint in a way no human would. None of that is a magic token; all of it moves the number Google hands the site.&lt;/p&gt;

&lt;p&gt;For the wider approach to staying unblocked, see &lt;a href="https://blog.peak.fo/how-to-scrape-cloudflare-protected-sites-without-getting-blocked/" rel="noopener noreferrer"&gt;scraping without getting blocked&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can you solve reCAPTCHA v3?
&lt;/h3&gt;

&lt;p&gt;Not in the puzzle sense; there's nothing to click. v3 always issues a token, but with a score from 0.0 to 1.0. The goal is a token that scores high enough to clear the site's threshold, which comes from a legitimate-looking browser and IP rather than from cracking anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good reCAPTCHA v3 score?
&lt;/h3&gt;

&lt;p&gt;Sites set their own threshold; 0.5 is a common cutoff, with higher scores waved through and lower ones blocked or stepped up. Enterprise adds reason codes and richer signals on top of the score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Peak support reCAPTCHA v3 Enterprise?
&lt;/h3&gt;

&lt;p&gt;Not yet. Peak currently solves Cloudflare Turnstile and the 5-second challenge; v3 Enterprise is on the near-term roadmap. Watch the docs, or see &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=recaptcha-v3-enterprise#pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; for what's live today.&lt;/p&gt;

&lt;p&gt;Solving Cloudflare today, reCAPTCHA v3 soon. Grab a key free at &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=recaptcha-v3-enterprise" rel="noopener noreferrer"&gt;peak.fo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webscraping</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Residential vs Datacenter Proxies for Solving CAPTCHAs</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Thu, 24 Sep 2026 07:21:54 +0000</pubDate>
      <link>https://dev.to/peakfodev/residential-vs-datacenter-proxies-for-solving-captchas-34nj</link>
      <guid>https://dev.to/peakfodev/residential-vs-datacenter-proxies-for-solving-captchas-34nj</guid>
      <description>&lt;p&gt;Cross-posted from &lt;a href="https://blog.peak.fo/residential-vs-datacenter-proxies-for-solving/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on &lt;a href="https://peak.fo/?ref=blog.peak.fo&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=residential-vs-datacenter" rel="noopener noreferrer"&gt;Peak&lt;/a&gt;, a Cloudflare Turnstile/5-second-challenge solving API, so take the proxy recommendations below with that in mind — the underlying tradeoffs hold regardless of which solver you use.&lt;/p&gt;




&lt;p&gt;Your solves work perfectly in testing. You ship to production, point it at real volume, and suddenly half of them fail. Nine times out of ten the code didn't change; the proxy did. The IP you solve through is part of what Cloudflare scores, and datacenter versus residential is the decision most people get wrong in one direction or the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick answer up front.&lt;/strong&gt; Datacenter proxies are fine for a lot of Turnstile work and much cheaper. Residential (or mobile) proxies earn their cost on strict targets and anything that binds to your IP, like the &lt;code&gt;cf_clearance&lt;/code&gt; cookie. Start cheap, escalate only where the data tells you to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually differs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Datacenter&lt;/th&gt;
&lt;th&gt;Residential / mobile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the IP lives&lt;/td&gt;
&lt;td&gt;A cloud or hosting provider&lt;/td&gt;
&lt;td&gt;A real ISP or carrier subscriber&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare's default trust&lt;/td&gt;
&lt;td&gt;Lower; flagged as hosting&lt;/td&gt;
&lt;td&gt;Higher; looks like a real visitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Cheap, often flat&lt;/td&gt;
&lt;td&gt;Expensive, usually per GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Fast, stable&lt;/td&gt;
&lt;td&gt;Slower, more variable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;High-volume, less strict targets&lt;/td&gt;
&lt;td&gt;Strict sites, IP-bound clearance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cloudflare keeps a reputation signal on IP ranges. Datacenter ranges are known to be datacenter, so they start from a lower baseline. That doesn't mean they fail; plenty of Turnstile targets issue tokens fine from datacenter IPs when the rest of the request looks right. It means the margin is thinner, so on a strict site a datacenter IP tips over into a challenge where a residential one sails through.&lt;/p&gt;

&lt;h2&gt;
  
  
  When datacenter is the right call
&lt;/h2&gt;

&lt;p&gt;If you're pulling public data at volume from targets that aren't especially aggressive, datacenter proxies are the economical choice. They're fast, stable, and cheap enough that you can rotate widely. For a lot of Turnstile token work, that's all you need. Spending on residential here is burning money for reliability you already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you need residential or mobile
&lt;/h2&gt;

&lt;p&gt;Two situations flip the decision.&lt;/p&gt;

&lt;p&gt;First, strict targets that challenge datacenter ranges on sight.&lt;/p&gt;

&lt;p&gt;Second, and this is the one people miss, anything that binds to your IP. The Cloudflare 5-second challenge issues a &lt;code&gt;cf_clearance&lt;/code&gt; cookie tied to the exact IP that earned it, so you need that IP to stay yours for the life of the session. A rotating datacenter pool breaks this instantly; the next request comes from a new IP and the clearance is void. The mechanics are in &lt;a href="https://blog.peak.fo/the-cf_clearance-cookie-explained-and-how-to-reuse-it/" rel="noopener noreferrer"&gt;the cf_clearance cookie, explained&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sticky vs rotating matters as much as the type
&lt;/h2&gt;

&lt;p&gt;A residential proxy that rotates every request is useless for clearance work, and a sticky datacenter session can outperform rotating residential for a flow that needs IP continuity. Match the session to the job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One-shot Turnstile token:&lt;/strong&gt; rotating is fine; each solve stands alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cf_clearance&lt;/code&gt; reuse across many requests:&lt;/strong&gt; sticky, so the IP holds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A login or multi-step flow:&lt;/strong&gt; sticky for the whole session so cookies and IP stay consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Peak passes your proxy straight through on the &lt;code&gt;proxy&lt;/code&gt; field, so you control the type and stickiness per request. Use a sticky residential proxy for 5-second clearance, a cheap datacenter one for high-volume token pulls, and pay for residential only where it changes the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  A sensible default
&lt;/h2&gt;

&lt;p&gt;Start every new target on datacenter. Watch the success rate. If it's clean, you just saved a fortune in bandwidth. If failures cluster, move that target to sticky residential and compare. Let the numbers pick, target by target, instead of paying the residential premium everywhere out of caution.&lt;/p&gt;

&lt;p&gt;The broader anti-blocking playbook is in &lt;a href="https://blog.peak.fo/how-to-scrape-cloudflare-protected-sites-without-getting-blocked/" rel="noopener noreferrer"&gt;scraping Cloudflare-protected sites without getting blocked&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need residential proxies to solve Cloudflare Turnstile?&lt;/strong&gt;&lt;br&gt;
Not always. Many Turnstile targets issue tokens fine from datacenter IPs. Residential pays off on strict sites and on anything IP-bound like the &lt;code&gt;cf_clearance&lt;/code&gt; cookie. Start datacenter and escalate where failures cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does &lt;code&gt;cf_clearance&lt;/code&gt; need a sticky proxy?&lt;/strong&gt;&lt;br&gt;
Cloudflare binds the clearance cookie to the IP that earned it. If your proxy rotates, the next request comes from a different IP and the cookie is rejected. A sticky session keeps the IP constant for the cookie's life.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are datacenter proxies always worse?&lt;/strong&gt;&lt;br&gt;
No. They're cheaper, faster, and stable, and they work on many targets. They only lose on strict sites and IP-bound flows. Pick per target, not by blanket rule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://peak.fo/?ref=blog.peak.fo&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=residential-vs-datacenter#pricing" rel="noopener noreferrer"&gt;See pricing to start&lt;/a&gt;. Bring your own proxy, pay only for solves that land. Grab a key free at &lt;a href="https://peak.fo/?ref=blog.peak.fo&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=residential-vs-datacenter" rel="noopener noreferrer"&gt;peak.fo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Cloudflare Turnstile vs reCAPTCHA vs hCaptcha (2026): which should you use?</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Wed, 23 Sep 2026 07:26:03 +0000</pubDate>
      <link>https://dev.to/peakfodev/cloudflare-turnstile-vs-recaptcha-vs-hcaptcha-2026-which-should-you-use-n0e</link>
      <guid>https://dev.to/peakfodev/cloudflare-turnstile-vs-recaptcha-vs-hcaptcha-2026-which-should-you-use-n0e</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/cloudflare-turnstile-vs-recaptcha-vs-hcaptcha-2026/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, the Turnstile-solving API mentioned below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three products, one job: tell humans and bots apart without making real people hate your site. Cloudflare Turnstile, Google reCAPTCHA, and hCaptcha go about it differently, and the right pick depends on whether you're the one adding the widget or the one automating against it.&lt;/p&gt;

&lt;p&gt;Quick verdict. Pick &lt;strong&gt;Turnstile&lt;/strong&gt; if you want free, privacy-friendly, and low-friction and you can live in Cloudflare's ecosystem. Pick &lt;strong&gt;reCAPTCHA&lt;/strong&gt; if you're deep in Google's stack and want the score-based v3 signals. Pick &lt;strong&gt;hCaptcha&lt;/strong&gt; if you need enterprise features like EU-region processing or you want the publisher rewards angle. If you're on the automation side, the calculus flips, and I'll cover that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  How each one decides you're human
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA v2 is the "click the checkbox, then maybe pick the buses" one. v3 dropped the puzzle entirely and returns a score from 0 to 1 that you act on server-side. hCaptcha looks a lot like reCAPTCHA v2 to a user (image grids), but it's a separate company with its own infrastructure, and it built a business around paying site owners for the labeling work.&lt;/p&gt;

&lt;p&gt;Turnstile took the other road. Most of the time it shows nothing, or a checkbox that resolves on its own. It runs JavaScript to read the browser environment, leans on Cloudflare's network reputation for your IP, and hands back a token. No image labeling, no "select all crosswalks."&lt;/p&gt;

&lt;h2&gt;
  
  
  Side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cloudflare Turnstile&lt;/th&gt;
&lt;th&gt;Google reCAPTCHA&lt;/th&gt;
&lt;th&gt;hCaptcha&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User experience&lt;/td&gt;
&lt;td&gt;Usually invisible / self-ticking&lt;/td&gt;
&lt;td&gt;v2 puzzles, v3 invisible score&lt;/td&gt;
&lt;td&gt;Image grids (v2-like)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost to the site&lt;/td&gt;
&lt;td&gt;Free at any scale&lt;/td&gt;
&lt;td&gt;Free tier, paid Enterprise&lt;/td&gt;
&lt;td&gt;Free tier, paid Enterprise; publisher rewards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy&lt;/td&gt;
&lt;td&gt;No third-party ad tracking&lt;/td&gt;
&lt;td&gt;Ties into Google&lt;/td&gt;
&lt;td&gt;More privacy-focused than Google&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires an account&lt;/td&gt;
&lt;td&gt;Cloudflare account&lt;/td&gt;
&lt;td&gt;Google account&lt;/td&gt;
&lt;td&gt;No account needed to embed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signal style&lt;/td&gt;
&lt;td&gt;Environment + IP reputation&lt;/td&gt;
&lt;td&gt;Behavior + Google graph (v3 score)&lt;/td&gt;
&lt;td&gt;Behavior + image challenge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For site owners the honest read is: Turnstile wins on price and friction, reCAPTCHA wins if you already live in Google's world, and hCaptcha wins on enterprise/region controls and the rewards program. None of them is a silver bullet against determined bots, because the token is only as strong as the environment and IP behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From the automation side: which is hardest to solve?
&lt;/h2&gt;

&lt;p&gt;This is where most comparison posts go quiet, so here's the practitioner view. For legitimate automation, scraping public data, or QA on your own forms, the three behave differently under a solver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turnstile&lt;/strong&gt; is a token problem. You need a valid token for the sitekey from an IP Cloudflare doesn't flag. No image labeling in the loop, so it's fast to solve programmatically, usually about a second with a clean proxy. We wrote the full walkthrough in &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;how to solve Cloudflare Turnstile&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;reCAPTCHA v2&lt;/strong&gt; historically leaned on image recognition, so solving it meant either a machine-vision model or a human-in-the-loop. &lt;strong&gt;v3&lt;/strong&gt; is score-based, which makes it less about "solving a puzzle" and more about presenting a trustworthy session, which is a different and often harder engineering problem. reCAPTCHA v3 Enterprise raises that bar again, and it's the next challenge type &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=turnstile-vs-recaptcha-hcaptcha#pricing" rel="noopener noreferrer"&gt;Peak is bringing online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;hCaptcha&lt;/strong&gt; sits closer to reCAPTCHA v2 in shape: image challenges that need vision or human solvers. Doable, but heavier per solve than a token-only widget like Turnstile.&lt;/p&gt;

&lt;p&gt;So if you're choosing what to &lt;em&gt;put on your site&lt;/em&gt; to slow bots, the ranking of "annoying to automate" isn't fixed; it depends on the attacker's tooling. If you're the one automating legitimately, Turnstile is typically the cleanest to handle because it's token-based, and a solving API returns that token in about a second at a tenth of a cent, billed only when it lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which should you actually use?
&lt;/h2&gt;

&lt;p&gt;If you run a site and you're already on Cloudflare, Turnstile is the easy default: free, quiet, and privacy-clean. If your analytics and ads already run through Google and you want v3 scoring baked into risk decisions, reCAPTCHA fits. If you have a compliance need for EU processing or you want to earn from the labeling, hCaptcha earns its slot. There isn't a universally "best" one, and anyone who tells you otherwise is selling something.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Turnstile better than reCAPTCHA?
&lt;/h3&gt;

&lt;p&gt;For most sites, Turnstile is friendlier: free at any scale, usually invisible, and it doesn't hand data to an ad network. reCAPTCHA's edge is v3's score signal and tight Google integration. Different strengths, not a clean win either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which CAPTCHA is easiest to solve for automation?
&lt;/h3&gt;

&lt;p&gt;Token-based challenges like Turnstile are typically the cleanest to handle programmatically, because there's no image labeling in the loop. Image-grid challenges (reCAPTCHA v2, hCaptcha) need vision models or human solvers and cost more per solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Peak support all three?
&lt;/h3&gt;

&lt;p&gt;Peak solves Cloudflare Turnstile and the Cloudflare 5-second challenge today, with reCAPTCHA v3 Enterprise coming next. See &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=turnstile-vs-recaptcha-hcaptcha#pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; and the &lt;a href="https://peak.fo/docs/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=turnstile-vs-recaptcha-hcaptcha" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Need to clear Turnstile at volume? Start free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=turnstile-vs-recaptcha-hcaptcha" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, or read the&lt;/em&gt; &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;&lt;em&gt;Turnstile solving guide&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>cf-turnstile-response: the token explained, and how to get one</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Tue, 22 Sep 2026 07:15:05 +0000</pubDate>
      <link>https://dev.to/peakfodev/cf-turnstile-response-the-token-explained-and-how-to-get-one-kki</link>
      <guid>https://dev.to/peakfodev/cf-turnstile-response-the-token-explained-and-how-to-get-one-kki</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/the-cf-turnstile-response-token-explained/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, a pay-per-solve Turnstile API — disclosing that up front.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You open the page source, find a hidden input called &lt;code&gt;cf-turnstile-response&lt;/code&gt;, and it's empty. Submit the form without filling it and the server bounces you. That one field is the whole handshake between Cloudflare Turnstile and the site behind it, and once you understand what goes in it, solving Turnstile stops feeling like magic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cf-turnstile-response&lt;/code&gt; is the token Cloudflare Turnstile hands your browser after it decides you're allowed through. The widget runs its checks, and when it's satisfied it drops a long string into that hidden field. The server then sends that string to Cloudflare's &lt;code&gt;siteverify&lt;/code&gt; endpoint to confirm it's real before trusting the request. No valid token, no entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the token comes from
&lt;/h2&gt;

&lt;p&gt;When a page loads Turnstile, it renders a widget tied to a &lt;strong&gt;sitekey&lt;/strong&gt; (the public identifier for that site's Turnstile config). The widget scores the browser in the background. If it passes, Turnstile writes the token into an input named &lt;code&gt;cf-turnstile-response&lt;/code&gt;, usually inside the form you're about to submit, and optionally fires a JavaScript callback with the same value.&lt;/p&gt;

&lt;p&gt;So the token has three properties that matter for automation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's &lt;strong&gt;tied to the sitekey&lt;/strong&gt; it was issued for. A token from one sitekey won't validate against another.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;single-use&lt;/strong&gt;. The server redeems it once at &lt;code&gt;siteverify&lt;/code&gt;; replay the same token and Cloudflare rejects it.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;short-lived&lt;/strong&gt;. Turnstile tokens expire around 300 seconds after issue. Grab one and sit on it, and it goes stale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're not sure where the sitekey on your target comes from, that's a two-minute job covered in &lt;a href="https://blog.peak.fo/how-to-find-a-cloudflare-turnstile-sitekey/" rel="noopener noreferrer"&gt;how to find a Turnstile sitekey&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the token gets submitted
&lt;/h2&gt;

&lt;p&gt;In the normal browser flow, the widget fills the field and you just submit the form. The token rides along as a POST parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/login&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;

email=you@example.com&amp;amp;password=...&amp;amp;cf-turnstile-response=0.abc123LongOpaqueString...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the server, the site takes that value and verifies it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# server side, on the site you're submitting to
POST https://challenges.cloudflare.com/turnstile/v0/siteverify
  secret=&amp;lt;the site's secret key&amp;gt;
  response=0.abc123LongOpaqueString...
# -&amp;gt; {"success": true, ...}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You never see the secret key; that's the site's. Your job as the client is only to produce a valid &lt;code&gt;response&lt;/code&gt; value and send it with the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the token without running a browser
&lt;/h2&gt;

&lt;p&gt;You have two ways to produce that token. Drive a real browser, let the widget solve, and scrape the field. Or ask a solving API to return the token directly. The second skips keeping a headless browser alive for every request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turnstiletask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAAAxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@ip:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# now submit it before it expires (~300s)
&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;you@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a typical target that comes back in about one to one and a half seconds, and Peak only charges when the solve lands, so a miss costs nothing. The token you get is a normal &lt;code&gt;cf-turnstile-response&lt;/code&gt; value; the server can't tell it apart from one a browser produced, because it isn't different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a token gets rejected
&lt;/h2&gt;

&lt;p&gt;Almost every "my token doesn't work" case is one of four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It expired.&lt;/strong&gt; You solved, then waited too long to submit. Solve immediately before the request that needs it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You reused it.&lt;/strong&gt; Tokens are single-use. Every submission needs a fresh one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong sitekey.&lt;/strong&gt; You solved against a sitekey that isn't the one on the page you're posting to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mismatched context.&lt;/strong&gt; Some deployments tie the token to an &lt;code&gt;action&lt;/code&gt; or the page's IP. Solve through the same proxy you'll submit from, and pass the &lt;code&gt;action&lt;/code&gt; if the widget sets one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is cf-turnstile-response?
&lt;/h3&gt;

&lt;p&gt;It's the hidden form field that holds the token Cloudflare Turnstile issues when a browser passes the challenge. The site sends that token to Cloudflare's siteverify endpoint to confirm the visitor cleared Turnstile before accepting the request.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long is a Turnstile token valid?
&lt;/h3&gt;

&lt;p&gt;Around 300 seconds. It's also single-use, so you need a fresh token for each submission and you should submit it right after you get it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I reuse a cf-turnstile-response token?
&lt;/h3&gt;

&lt;p&gt;No. The server redeems it once at siteverify. Solve again for the next request. See &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;the Turnstile solving guide&lt;/a&gt; for the full flow, or &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cf-turnstile-response-token#pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; to start.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Need Turnstile tokens on demand? Grab a key free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cf-turnstile-response-token" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webscraping</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What is Cloudflare Turnstile? How it works, and what the integration looks like</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Mon, 21 Sep 2026 07:14:34 +0000</pubDate>
      <link>https://dev.to/peakfodev/what-is-cloudflare-turnstile-how-it-works-and-what-the-integration-looks-like-7mf</link>
      <guid>https://dev.to/peakfodev/what-is-cloudflare-turnstile-how-it-works-and-what-the-integration-looks-like-7mf</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/what-is-cloudflare-turnstile-how-it-works/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, the solving API mentioned at the end.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cloudflare Turnstile is a CAPTCHA replacement that decides whether a visitor is human by watching how their browser behaves, instead of making them label images. Most visitors see nothing more than a checkbox that ticks itself. Behind that checkbox, Cloudflare runs a set of browser checks and issues a token the site uses to confirm the visit is legitimate.&lt;/p&gt;

&lt;p&gt;Below: what it checks, the three modes, what the integration looks like from the site's side, and why so many sites switched to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Turnstile works
&lt;/h2&gt;

&lt;p&gt;When a page loads Turnstile, a small script runs in the visitor's browser. It looks at signals the browser gives off: the JavaScript environment, how rendering behaves, and subtle traits that separate a real browser from a headless script. Cloudflare pairs that with its own reputation data for the visitor's IP, since it sits in front of a large chunk of the web and has seen that address before.&lt;/p&gt;

&lt;p&gt;If the checks pass, Turnstile writes a token into a hidden field called &lt;code&gt;cf-turnstile-response&lt;/code&gt;. The site sends that token to its server, the server asks Cloudflare to verify it, and the visit proceeds. No puzzle, no images, usually no interruption. When Cloudflare is less sure, it may show an actual checkbox to click, but the heavy lifting is invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three modes
&lt;/h2&gt;

&lt;p&gt;Turnstile comes in three flavors, and the difference is how visible it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-interactive:&lt;/strong&gt; runs silently, no click. The common case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed:&lt;/strong&gt; Cloudflare decides in the moment whether to show a checkbox, based on how risky the visit looks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invisible:&lt;/strong&gt; no widget renders at all. The check happens in the background.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three end the same way: a token that proves the check ran. The mode only changes whether the visitor notices.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like on the page
&lt;/h2&gt;

&lt;p&gt;From the site owner's side, there are two pieces. First, the client script and a widget element carrying the public sitekey:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://challenges.cloudflare.com/turnstile/v0/api.js"&lt;/span&gt; &lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cf-turnstile"&lt;/span&gt; &lt;span class="na"&gt;data-sitekey=&lt;/span&gt;&lt;span class="s"&gt;"0x4AAAAAAAxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Log in&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the widget finishes, it adds the &lt;code&gt;cf-turnstile-response&lt;/code&gt; field to the form, so the token rides along with the POST. Second, the server sends that token to Cloudflare's &lt;code&gt;siteverify&lt;/code&gt; endpoint together with the site's secret key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://challenges.cloudflare.com/turnstile/v0/siteverify &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"secret=YOUR_SECRET_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"response=TOKEN_FROM_THE_FORM"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON that comes back has &lt;code&gt;"success": true&lt;/code&gt; or an error code. Two details from Cloudflare's docs matter later if you automate against it: a token expires after 300 seconds, and each token can be validated only once. A replayed token is rejected.&lt;/p&gt;

&lt;p&gt;The sitekey is public and sits in the page source. The secret key stays on the server, which is why a token can't be forged by someone who only has the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why sites use it
&lt;/h2&gt;

&lt;p&gt;Two reasons carry most of the switch. It's free with unlimited challenges, which is not true of reCAPTCHA Enterprise or hCaptcha's paid tiers. And it doesn't hand visitor data to an advertising network, which matters for privacy and for the regulations around it. Add that most people never have to solve anything, and you get fewer abandoned forms. It also doesn't require the site to route its traffic through Cloudflare: you need a Cloudflare account to get keys, and you can drop the widget into any page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Turnstile is not
&lt;/h2&gt;

&lt;p&gt;It isn't a wall. Turnstile raises the cost of automated abuse; it doesn't make it impossible. The token is only as trustworthy as the browser and IP behind it, which is why, on the automation side, solving Turnstile comes down to presenting a clean environment and a good IP rather than defeating a puzzle.&lt;/p&gt;

&lt;p&gt;If you're doing legitimate automation, scraping public data, or testing your own forms, see &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;how to solve Cloudflare Turnstile&lt;/a&gt; for the practical version. Curious how it compares with the alternatives? We wrote up &lt;a href="https://blog.peak.fo/cloudflare-turnstile-vs-recaptcha-vs-hcaptcha-2026/" rel="noopener noreferrer"&gt;Turnstile, reCAPTCHA, and hCaptcha&lt;/a&gt; side by side.&lt;/p&gt;

&lt;p&gt;If you'd rather not run the browser side yourself, Peak's API takes the sitekey and page URL and returns a token you put in the &lt;code&gt;cf-turnstile-response&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turnstiletask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAAAxxxxxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pricing is $0.90 per 1,000 successful solves, dropping to $0.35 at the largest package, and failed solves aren't billed. New accounts get 1,000 free solves to test with. Parameter reference is in the &lt;a href="https://peak.fo/docs/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=what-is-turnstile" rel="noopener noreferrer"&gt;Peak docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is Cloudflare Turnstile in simple terms?
&lt;/h3&gt;

&lt;p&gt;It's a CAPTCHA that checks your browser in the background and gives the site a token proving you're likely human, instead of asking you to solve a puzzle. Most visitors just see a checkbox that completes on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Cloudflare Turnstile free?
&lt;/h3&gt;

&lt;p&gt;Yes, it's free for site owners with unlimited challenges, which is a big part of why it's grown so fast against paid options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Turnstile track users?
&lt;/h3&gt;

&lt;p&gt;Turnstile is built to collect minimal data and doesn't feed a third-party ad network, which is one of its main selling points over reCAPTCHA.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does a Turnstile token last?
&lt;/h3&gt;

&lt;p&gt;300 seconds, and it can be verified once. After that you need a fresh token.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Working with Turnstile in your own automation? Start free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=what-is-turnstile" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webdev</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Find a Cloudflare Turnstile Sitekey (Including When JavaScript Loads It)</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Sun, 20 Sep 2026 07:15:06 +0000</pubDate>
      <link>https://dev.to/peakfodev/how-to-find-a-cloudflare-turnstile-sitekey-including-when-javascript-loads-it-259d</link>
      <guid>https://dev.to/peakfodev/how-to-find-a-cloudflare-turnstile-sitekey-including-when-javascript-loads-it-259d</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/how-to-find-a-cloudflare-turnstile-sitekey/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, the solving API used in the example below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before you can solve a Cloudflare Turnstile, you need its sitekey. It's a short public string, real ones start with &lt;code&gt;0x4&lt;/code&gt;, and it's sitting in the page's HTML in plain sight. Here's how to grab it in a few seconds, and what to do when the widget is loaded dynamically and doesn't show up in the raw source.&lt;/p&gt;

&lt;p&gt;Quick answer: open the page, search the HTML for &lt;code&gt;data-sitekey&lt;/code&gt; or &lt;code&gt;0x4&lt;/code&gt;, and copy the value. That's the sitekey. The rest of this covers the cases where it isn't that simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fast way: read it off the widget
&lt;/h2&gt;

&lt;p&gt;Turnstile renders into an element that carries the key as a &lt;code&gt;data-sitekey&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cf-turnstile"&lt;/span&gt; &lt;span class="na"&gt;data-sitekey=&lt;/span&gt;&lt;span class="s"&gt;"0x4AAAAAAAxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a browser, open DevTools, hit the elements panel, and search (Ctrl+F inside it) for &lt;code&gt;cf-turnstile&lt;/code&gt; or &lt;code&gt;data-sitekey&lt;/code&gt;. Copy the value. Done. If you'd rather stay in the terminal, curl the page and grep for the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://target.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s1"&gt;'data-sitekey="[^"]+"'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When it's not in the raw HTML
&lt;/h2&gt;

&lt;p&gt;Plenty of sites load Turnstile with JavaScript after the page renders, or configure it through the &lt;code&gt;turnstile.render()&lt;/code&gt; call instead of a data attribute. Then a plain curl shows nothing, because the key only appears once scripts run. Two options.&lt;/p&gt;

&lt;p&gt;Search the JavaScript. The key still has to reach the browser, so grep the page's scripts for &lt;code&gt;sitekey&lt;/code&gt; or the &lt;code&gt;0x4AAAAAAA&lt;/code&gt; prefix. It's often passed as an argument to &lt;code&gt;turnstile.render()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;turnstile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x4AAAAAAAxxxxxxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or read it from a real browser. Load the page in a headless browser, wait for the widget, and pull the attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[data-sitekey]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sitekey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[data-sitekey]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data-sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a third place to look. Once the widget has rendered, it loads a frame from &lt;code&gt;challenges.cloudflare.com&lt;/code&gt;, and the sitekey is one of the path segments in that frame's URL, for example &lt;code&gt;.../turnstile/f/av0/rch/fmb9f/1x00000000000000000000AA/light/fbE/new/normal&lt;/code&gt;. The iframe sits inside a closed shadow root, so &lt;code&gt;querySelector&lt;/code&gt; won't find it, but the frame list will. In Playwright that's &lt;code&gt;[f.url for f in page.frames]&lt;/code&gt;. In DevTools, expand the widget's &lt;code&gt;#shadow-root&lt;/code&gt; and read the iframe &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test keys look different
&lt;/h2&gt;

&lt;p&gt;Cloudflare publishes dummy sitekeys for development, and some demo pages use them. They don't start with &lt;code&gt;0x4&lt;/code&gt;, so a grep for that prefix will miss them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sitekey&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1x00000000000000000000AA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;always passes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2x00000000000000000000AB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;always blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3x00000000000000000000FF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;forces an interactive challenge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I checked this against Cloudflare's own demo page (&lt;code&gt;demo.turnstile.workers.dev&lt;/code&gt;): its widget uses &lt;code&gt;1x00000000000000000000AA&lt;/code&gt;. If you're pulling keys out of a page and get one of these, you're looking at a test widget, not a protected form.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sitekey isn't a secret
&lt;/h2&gt;

&lt;p&gt;Worth saying plainly: the sitekey is public by design. It's the client-side identifier the widget needs, so it's always exposed in the page. The private half (the secret key) lives on the site's server and never reaches you, and you don't need it. To solve the challenge you only need the public sitekey plus the page URL.&lt;/p&gt;

&lt;p&gt;Once you have both, you're ready to solve. The full flow, with code and the token submission step, is in &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;how to solve Cloudflare Turnstile&lt;/a&gt;, and the parameter reference is in the &lt;a href="https://peak.fo/docs/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=find-sitekey" rel="noopener noreferrer"&gt;Peak docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the whole thing with Peak's API, which is the one I work on. Send the sitekey and page URL, get a token back, and put it in the form's &lt;code&gt;cf-turnstile-response&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turnstiletask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAAAxxxxxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pricing is $0.90 per 1,000 successful solves, dropping to $0.35 at the largest package, and failed solves aren't billed. New accounts get 1,000 free solves to test with.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where is the Turnstile sitekey?
&lt;/h3&gt;

&lt;p&gt;In the page HTML, as the &lt;code&gt;data-sitekey&lt;/code&gt; attribute on the widget element, or passed to &lt;code&gt;turnstile.render()&lt;/code&gt; in the page's JavaScript. Production keys start with &lt;code&gt;0x4&lt;/code&gt;; Cloudflare's dummy test keys don't (see above).&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the sitekey secret?
&lt;/h3&gt;

&lt;p&gt;No. The sitekey is public and always visible in the page. The secret key is separate, stays on the site's server, and you never need it to solve the challenge.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have the sitekey and URL? Start solving free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=find-sitekey" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webscraping</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The cf_clearance cookie, explained (and how to reuse it without getting re-challenged)</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Sat, 12 Sep 2026 07:16:46 +0000</pubDate>
      <link>https://dev.to/peakfodev/the-cfclearance-cookie-explained-and-how-to-reuse-it-without-getting-re-challenged-1j9h</link>
      <guid>https://dev.to/peakfodev/the-cfclearance-cookie-explained-and-how-to-reuse-it-without-getting-re-challenged-1j9h</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/the-cf_clearance-cookie-explained-and-how-to-reuse-it/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, the solving API used in the example below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You solve Cloudflare's "checking your browser" page once, and then every follow-up request gets challenged again. The thing you're missing is a cookie: &lt;code&gt;cf_clearance&lt;/code&gt;. Hold onto it correctly and the rest of your session sails through. Handle it wrong and you're stuck in a loop.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cf_clearance&lt;/code&gt; is the cookie Cloudflare sets after a browser passes the 5-second interstitial (or a managed challenge). It's proof that this visitor cleared the check, so Cloudflare stops re-challenging. For automation, that cookie is the whole prize: get it once, reuse it, skip the challenge on subsequent requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you can't just copy the cookie and go
&lt;/h2&gt;

&lt;p&gt;Here's the part that trips people up. &lt;code&gt;cf_clearance&lt;/code&gt; isn't portable. Cloudflare binds it to two things: the &lt;strong&gt;IP address&lt;/strong&gt; that earned it and the &lt;strong&gt;user-agent&lt;/strong&gt; of the browser that earned it. Move the cookie to a different IP or send it with a different user-agent, and Cloudflare throws it out and challenges you again.&lt;/p&gt;

&lt;p&gt;So reuse has three rules. Same cookie, same IP, same user-agent. Break any one and it stops working. That's why a sticky session proxy matters here: you need the IP that got the clearance to stay yours for the life of the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting cf_clearance with a solving API
&lt;/h2&gt;

&lt;p&gt;You can drive a real browser to earn the cookie, or call an API that returns it. With Peak, the &lt;code&gt;cloudflare5stask&lt;/code&gt; gives you back the cookie, the matching user-agent, and the supporting headers in one response, so you don't have to keep a browser alive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;            &lt;span class="c1"&gt;# trailing slash is required
&lt;/span&gt;&lt;span class="n"&gt;PROXY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@ip:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;        &lt;span class="c1"&gt;# sticky session: same IP for the whole session
&lt;/span&gt;
&lt;span class="n"&gt;sol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cloudflare5stask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROXY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;user_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# reuse this exact string
&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROXY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROXY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# and this exact proxy
&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_agent&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cookies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  &lt;span class="c1"&gt;# cf_clearance + __cf_bm
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# finalize the challenge once: POST the returned attributes back to the page
&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attributes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Referer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?__cf_chl_tk=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cf_rt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# from here on, plain requests with the same cookies, user-agent and IP go through
&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/protected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response carries &lt;code&gt;cf_clearance&lt;/code&gt; plus &lt;code&gt;__cf_bm&lt;/code&gt; (Cloudflare's bot-management cookie), the user-agent string that matches them, and a set of &lt;code&gt;attributes&lt;/code&gt; you POST back to the page once to finalize the challenge. Set all of it together. The most common mistake is grabbing &lt;code&gt;cf_clearance&lt;/code&gt; alone and sending it with your own default user-agent, which doesn't match, so Cloudflare rejects it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How long does it last?
&lt;/h2&gt;

&lt;p&gt;Clearance cookies are short-lived by design, and Cloudflare tunes the lifetime per site, so don't hard-code an assumption. Treat it as a session token: use it while it works, catch the moment a request gets challenged again, and solve once more to refresh. Because Peak only bills successful solves, a refresh here and there costs a tenth of a cent each ($1.00 per 1,000 successful solves for the 5-second task) and nothing when a solve misses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turnstile vs the 5-second challenge
&lt;/h2&gt;

&lt;p&gt;Don't confuse the two. Turnstile is a widget that returns a &lt;em&gt;token&lt;/em&gt; you submit with a form; the 5-second challenge is an interstitial that returns a &lt;em&gt;cookie&lt;/em&gt; you reuse on requests. Different mechanisms, different handling. If you're dealing with the widget instead, read &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;how to solve Cloudflare Turnstile&lt;/a&gt;. The full parameter reference for both is in the &lt;a href="https://peak.fo/docs/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cf-clearance" rel="noopener noreferrer"&gt;Peak docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the cf_clearance cookie?
&lt;/h3&gt;

&lt;p&gt;It's the cookie Cloudflare sets once a browser passes the 5-second or managed challenge. It tells Cloudflare this visitor already cleared the check, so subsequent requests aren't challenged, as long as they come from the same IP with the same user-agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I reuse cf_clearance across IPs?
&lt;/h3&gt;

&lt;p&gt;No. Cloudflare binds it to the IP and user-agent that earned it. Use a sticky session proxy so the IP stays constant, and always send the matching user-agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get cf_clearance automatically?
&lt;/h3&gt;

&lt;p&gt;Run the &lt;code&gt;cloudflare5stask&lt;/code&gt; against the protected URL with a sticky proxy; the response returns the cookie and the matching user-agent to reuse. See &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cf-clearance#pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; to start.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Clearing Cloudflare at scale? Grab a key free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=cf-clearance" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webscraping</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Handling Cloudflare Turnstile in Playwright and Puppeteer (inject the token, skip the stealth fight)</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Fri, 11 Sep 2026 07:16:07 +0000</pubDate>
      <link>https://dev.to/peakfodev/handling-cloudflare-turnstile-in-playwright-and-puppeteer-inject-the-token-skip-the-stealth-fight-328l</link>
      <guid>https://dev.to/peakfodev/handling-cloudflare-turnstile-in-playwright-and-puppeteer-inject-the-token-skip-the-stealth-fight-328l</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://blog.peak.fo/handling-turnstile-in-playwright-and-puppeteer/" rel="noopener noreferrer"&gt;blog.peak.fo&lt;/a&gt;. I work on Peak, the solving API used in the examples below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your Playwright script loads the page, the Turnstile widget sits there spinning, and it never flips to the green check. So the form won't submit, and your run dies at the gate. The frustrating part is that the same script works when you drive it by hand. That gap is the whole problem, and there are two clean ways to close it.&lt;/p&gt;

&lt;p&gt;Either you harden the browser enough that Turnstile trusts it and solves on its own, or you get the token out-of-band and inject it into the page. The second is more reliable under automation, because you stop fighting fingerprint detection and just hand the form what it needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the widget stalls under automation
&lt;/h2&gt;

&lt;p&gt;Turnstile scores the browser in the background. Headless Chromium leaks signals that a normal browser doesn't: automation flags, a thin canvas and WebGL fingerprint, timing that's too clean. When the score comes back low, the widget quietly refuses to issue a token. No error, just an endless spinner. Puppeteer has the same issue for the same reasons.&lt;/p&gt;

&lt;p&gt;You can chase this with stealth plugins, and sometimes it's enough. But you're now maintaining an anti-detection layer that breaks every time Cloudflare adjusts its model. If you only need the token, there's less moving machinery in getting it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inject a solved token into Playwright
&lt;/h2&gt;

&lt;p&gt;Solve with the API, then drop the token into the hidden &lt;code&gt;cf-turnstile-response&lt;/code&gt; field the form reads on submit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://target.com/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SITEKEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAAAxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PROXY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@ip:port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;solve_token&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turnstiletask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SITEKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PROXY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;solve_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# ~1 to 1.5s on a typical target
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;(token) =&amp;gt; {
            let el = document.querySelector(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[name=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;);
            if (!el) {
                el = document.createElement(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;);
                el.type = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;;
                el.name = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;;
                (document.forms[0] || document.body).appendChild(el);
            }
            el.value = token;
        }&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input[name=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;you@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input[name=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;button[type=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The form posts the token exactly as a browser-solved one would, because it is one. For the details of what that token is and why it expires, see &lt;a href="https://blog.peak.fo/the-cf-turnstile-response-token-explained/" rel="noopener noreferrer"&gt;the cf-turnstile-response token, explained&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same idea in Puppeteer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node + Puppeteer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;solveToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// POST to api.peak.fo/solve, read data.token&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name="cf-turnstile-response"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather not hand-roll the injection, the same flow is packaged: &lt;a href="https://pypi.org/project/playwright-turnstile/" rel="noopener noreferrer"&gt;playwright-turnstile&lt;/a&gt; on PyPI for Playwright, and &lt;a href="https://github.com/CircuitSavage/puppeteer-extra-plugin-turnstile" rel="noopener noreferrer"&gt;puppeteer-extra-plugin-turnstile&lt;/a&gt; on GitHub for Puppeteer. Both read the sitekey, request the token, set the field and fire the callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that trip people up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Callbacks.&lt;/strong&gt; Some pages don't read the field on submit; they act on a JavaScript callback set with &lt;code&gt;data-callback&lt;/code&gt;. If the button stays disabled after you set the field, find the callback name and call it: &lt;code&gt;page.evaluate((t) =&amp;gt; window.myTurnstileCb(t), token)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timing.&lt;/strong&gt; Turnstile tokens are single-use and expire in roughly 300 seconds. Solve right before you submit, not at the top of a long script, or the token goes stale before the form sees it.&lt;/p&gt;

&lt;p&gt;If you're weighing whether to keep the browser at all, &lt;a href="https://blog.peak.fo/captcha-solving-api-vs-headless-browser-which-to-use/" rel="noopener noreferrer"&gt;solving API vs headless browser&lt;/a&gt; lays out when each approach is the right call. The end-to-end flow lives in the &lt;a href="https://blog.peak.fo/how-to-solve-cloudflare-turnstile-in-2026-dev-guide/" rel="noopener noreferrer"&gt;Turnstile solving guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why won't Turnstile solve in headless Playwright?
&lt;/h3&gt;

&lt;p&gt;Headless Chromium leaks automation signals and a weak fingerprint, so Turnstile scores the browser low and never issues a token. Either harden the browser against detection or solve the token out-of-band and inject it into the cf-turnstile-response field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need stealth plugins?
&lt;/h3&gt;

&lt;p&gt;Not if you inject a solved token. Stealth helps when you want the widget itself to pass, but it needs ongoing maintenance as Cloudflare changes. Injecting a token sidesteps the fingerprint fight entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle a Turnstile callback?
&lt;/h3&gt;

&lt;p&gt;If the page uses data-callback, setting the hidden field may not be enough. Read the callback name from the widget config and call it with the token via page.evaluate. See &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=playwright-puppeteer#pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; to start.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Automating past Turnstile? Grab a key free at&lt;/em&gt; &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=playwright-puppeteer" rel="noopener noreferrer"&gt;&lt;em&gt;peak.fo&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>puppeteer</category>
      <category>webscraping</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>FlareSolverr not working in 2026? Why it times out, and what to use instead</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:19:20 +0000</pubDate>
      <link>https://dev.to/peakfodev/flaresolverr-not-working-in-2026-why-it-times-out-and-what-to-use-instead-59co</link>
      <guid>https://dev.to/peakfodev/flaresolverr-not-working-in-2026-why-it-times-out-and-what-to-use-instead-59co</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-posted from the &lt;a href="https://blog.peak.fo/flaresolverr-not-working-2026/" rel="noopener noreferrer"&gt;Peak blog&lt;/a&gt;. Disclosure: I work on Peak, the solve API mentioned below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;FlareSolverr used to be the easy answer: run a container, POST a URL, get back cookies and HTML. In 2026 you're more likely to get &lt;code&gt;Challenge not detected&lt;/code&gt;, a timeout, or a response that's still the "Just a moment…" page. The approach itself, driving a real browser through the challenge, is the thing that's breaking. Here's why, and what actually gets you the token.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;FlareSolverr solves the old interstitial by loading it in a headless browser and waiting for the redirect. Cloudflare Turnstile and the managed challenge now fingerprint the browser and score the IP, and a headless browser on a datacenter address fails both checks, so the redirect never comes and FlareSolverr times out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the browser approach breaks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It waits for a redirect that no longer happens.&lt;/strong&gt; FlareSolverr's model is "open the challenge page, wait for Cloudflare to clear it." Turnstile is an interactive widget, not a wait-and-redirect page, so there's nothing for it to wait on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headless fingerprints get flagged.&lt;/strong&gt; Cloudflare reads canvas, WebGL, audio, and behavioral signals. A stock headless Chrome, even a patched one, leaves tells the challenge is specifically looking for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter IPs score badly.&lt;/strong&gt; Even a perfect browser gets a low trust score from a datacenter or flagged IP, and the challenge stays up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance lags Cloudflare.&lt;/strong&gt; FlareSolverr depends on browser-patching projects keeping pace with detection changes. When Cloudflare ships an update, there's a window where everything downstream returns timeouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to confirm it's the challenge, not your setup
&lt;/h2&gt;

&lt;p&gt;If the FlareSolverr response &lt;code&gt;solution.response&lt;/code&gt; still contains &lt;code&gt;cf-turnstile&lt;/code&gt; or &lt;code&gt;Just a moment&lt;/code&gt;, the browser didn't pass. No amount of longer &lt;code&gt;maxTimeout&lt;/code&gt; will fix that, because it's not a timing problem. It's a detection problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: skip the browser fleet, get the token from an API
&lt;/h2&gt;

&lt;p&gt;Instead of maintaining a headless browser and hoping its fingerprint holds, send the sitekey and your proxy to a solve API and get the &lt;code&gt;cf-turnstile-response&lt;/code&gt; token back. One request, about a second, and the browser work happens on the solver's side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.peak.fo/solve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: pk_your_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"task_type":"turnstiletask","url":"https://target.example/","sitekey":"0x4AAA...","proxy":"http://user:pass@ip:port"}'&lt;/span&gt;

&lt;span class="c"&gt;# -&amp;gt; {"success": true, "data": {"token": "0.abc..."}, "cost": 0.0009}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You inject that token into the form's &lt;code&gt;cf-turnstile-response&lt;/code&gt; field (or send it to the site's verify endpoint) and continue. Passing your own proxy matters: Cloudflare ties clearance to the requesting IP, so the solve runs from the same address as your crawl.&lt;/p&gt;

&lt;p&gt;For the managed 5-second challenge, the same API returns a &lt;code&gt;cf_clearance&lt;/code&gt; cookie and the matching user-agent instead of a widget token. Set both on your session and re-request.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather keep it in your existing tooling
&lt;/h2&gt;

&lt;p&gt;You don't have to call the API directly. If your scraper is built on a common library, a wrapper does the detect-solve-inject for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/playwright-turnstile/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;playwright-turnstile&lt;/a&gt; and &lt;a href="https://pypi.org/project/selenium-turnstile/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;selenium-turnstile&lt;/a&gt; for browser automation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/cloudscraper-turnstile/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;cloudscraper-turnstile&lt;/a&gt; and &lt;a href="https://pypi.org/project/turnstile-curl/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;turnstile-curl&lt;/a&gt; for HTTP clients&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/scrapy-turnstile/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;scrapy-turnstile&lt;/a&gt; for Scrapy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood they all call the same one-request &lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;Peak&lt;/a&gt; API, so you get the token without running or patching a browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs vs. a self-hosted fleet
&lt;/h2&gt;

&lt;p&gt;FlareSolverr is free to install but not free to run. You pay in servers, residential proxies, and the hours spent chasing detection updates. A solve API is pay-per-success: from $0.90 per 1,000 down to $0.35 at volume, and failed solves cost nothing. New accounts get 1,000 free solves, no card, so you can measure the success rate on your own target before switching anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://peak.fo/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;Get a free key&lt;/a&gt; · &lt;a href="https://peak.fo/docs/turnstile?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;Turnstile docs&lt;/a&gt; · &lt;a href="https://peak.fo/docs/cloudflare-5s?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=flaresolverr" rel="noopener noreferrer"&gt;5s challenge docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Use this for automation, QA, monitoring, and scraping public data you're authorized to access. Respect each site's Terms of Service and robots.txt.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>cloudflare</category>
      <category>docker</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why your scraper gets a 403 on Cloudflare Turnstile (and the token-injection fix)</title>
      <dc:creator>Peak Fo</dc:creator>
      <pubDate>Mon, 07 Sep 2026 20:44:01 +0000</pubDate>
      <link>https://dev.to/peakfodev/why-your-scraper-gets-a-403-on-cloudflare-turnstile-and-the-token-injection-fix-59a</link>
      <guid>https://dev.to/peakfodev/why-your-scraper-gets-a-403-on-cloudflare-turnstile-and-the-token-injection-fix-59a</guid>
      <description>&lt;p&gt;Your scraper works fine against the site's HTML, then one day every request comes back &lt;code&gt;403&lt;/code&gt; — or the Turnstile widget just spins forever and never returns a token. Same code, same proxies, nothing changed on your end. Here's what's actually going on and the approach I've landed on after fighting it across a few dozen targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things get you blocked, not one
&lt;/h2&gt;

&lt;p&gt;Cloudflare Turnstile scores the browser, and separately Cloudflare scores the connection. A 403 usually means you failed one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fingerprint.&lt;/strong&gt; Turnstile runs JavaScript that pokes at the browser — canvas, WebGL, timing, the shape of your navigator object, whether events look human. A plain &lt;code&gt;requests&lt;/code&gt; or &lt;code&gt;httpx&lt;/code&gt; client runs none of that JS, so there's nothing to score and the challenge never clears. Headless Chrome runs the JS but leaks automation signals unless you go out of your way to hide them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP reputation.&lt;/strong&gt; This is the one people miss. Cloudflare keeps lists of datacenter ranges. If you're coming from AWS, GCP, Hetzner, OVH, or a cheap datacenter proxy, you can have a perfect fingerprint and still get thrown into an endless challenge, because the IP is the tell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A stealth browser can fix the first problem. It cannot fix the second. That's why people burn a weekend on &lt;code&gt;undetected-chromedriver&lt;/code&gt; tweaks and still get walls of 403s — they solved fingerprinting and left the IP problem untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The token is separate from your request
&lt;/h2&gt;

&lt;p&gt;The thing that unlocks a clean fix: the widget produces a token — &lt;code&gt;cf-turnstile-response&lt;/code&gt; — and on most deployments that token is validated &lt;strong&gt;server-side against the sitekey and the hostname&lt;/strong&gt;, not against the IP that produced it. The site's backend calls Cloudflare's &lt;code&gt;siteverify&lt;/code&gt; with the token and gets back pass/fail.&lt;/p&gt;

&lt;p&gt;So you don't have to make your scraper's browser pass the challenge. You need a valid token for that sitekey and hostname, produced by &lt;em&gt;something&lt;/em&gt; that can run the JS from a residential-looking IP, and then you attach it to your own request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the sitekey
&lt;/h2&gt;

&lt;p&gt;It's sitting in the page. Look for &lt;code&gt;data-sitekey&lt;/code&gt; on the Turnstile div, or a &lt;code&gt;render()&lt;/code&gt; call in the JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data-sitekey=[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\']([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\']+)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\']&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sitekey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sitekey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. 0x4AAAAAAA...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's not in the initial HTML it's injected by JS — open DevTools, filter network for &lt;code&gt;turnstile&lt;/code&gt;, and you'll see the sitekey in the &lt;code&gt;challenges.cloudflare.com&lt;/code&gt; request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Producing a token
&lt;/h2&gt;

&lt;p&gt;Two honest options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run a real browser yourself.&lt;/strong&gt; Playwright or Selenium with a genuine profile, pointed through a residential proxy, loading the page and reading the token out of the DOM after the widget resolves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# token = page.locator("[name=cf-turnstile-response]").input_value()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works when your fingerprint is clean &lt;em&gt;and&lt;/em&gt; your IP is residential. It's the cheapest per-token if you already own good proxies and don't mind babysitting browser instances. It's slow (a full browser per solve) and it breaks whenever Cloudflare ships a new challenge variant, which is often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call a solving API.&lt;/strong&gt; You hand it the URL and sitekey; it runs the browser farm on residential IPs and hands back a token. You keep your own scraper as a plain HTTP client. This is what I reach for when I care more about throughput than about owning the whole stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pk_your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TARGET&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/login&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SITEKEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x4AAAAAAA...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 1) get a token — comes back in ~1s
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.peak.fo/solve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TurnstileTaskProxyLess&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitekey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SITEKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solve failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# 2) attach the token to your real request, before it expires
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TARGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;you@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cf-turnstile-response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the target binds the token to the solving IP (some do), pass a &lt;code&gt;proxy&lt;/code&gt; field so the token is minted through the same residential IP you'll submit from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I work on Peak, the API in that second snippet, so take the mention with that in mind. The API shape is close to interchangeable across providers — the request keys differ but the flow (POST url+sitekey, poll or await, get token) is the same. On price, most providers cluster around $1.20 to $1.45 per 1,000 Turnstile solves. Peak is $0.90 per 1,000 successful solves, dropping toward $0.35 at volume, and you're only billed for tokens that actually come back valid, with about 1,000 free solves to test first. Try whichever; the code above changes by one URL and a couple of JSON keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four ways a valid token still fails
&lt;/h2&gt;

&lt;p&gt;Once you have a token and it &lt;em&gt;still&lt;/em&gt; doesn't work, it's almost always one of these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expired.&lt;/strong&gt; Turnstile tokens live about 300 seconds. If you solve, then sit in a queue for five minutes, you're submitting a dead token. Solve right before you submit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reused.&lt;/strong&gt; A token is single-use. The second request with the same &lt;code&gt;cf-turnstile-response&lt;/code&gt; fails. One solve, one submit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong sitekey.&lt;/strong&gt; You grabbed a sitekey from a different widget on the page, or from a cached older version. Re-pull it from the live page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP mismatch.&lt;/strong&gt; The site's backend compares the solving IP to the submitting IP. If yours differ, solve through the same proxy you submit from.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When none of this helps
&lt;/h2&gt;

&lt;p&gt;If the site validates far more than the token — device fingerprint tied to a session, behavioral signals across the whole flow, a &lt;code&gt;cf_clearance&lt;/code&gt; cookie you also need — then a bare token won't carry you, and you're back to driving a full, well-fingerprinted browser through the entire session. Turnstile is one gate; some sites stack several. Fix the token gate first, because it's the one that's cleanly separable, then see what's left.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://blog.peak.fo/cloudflare-turnstile-403-scraper-fix/" rel="noopener noreferrer"&gt;Peak blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>cloudflare</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
