<?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: Bob</title>
    <description>The latest articles on DEV Community by Bob (@capbypass).</description>
    <link>https://dev.to/capbypass</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%2F3984347%2F7b4ee5dd-248f-48f8-965a-0e1ab40ef569.png</url>
      <title>DEV Community: Bob</title>
      <link>https://dev.to/capbypass</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/capbypass"/>
    <language>en</language>
    <item>
      <title>why your reCAPTCHA v3 score is low</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 21:02:07 +0000</pubDate>
      <link>https://dev.to/capbypass/why-your-recaptcha-v3-score-is-low-3mpf</link>
      <guid>https://dev.to/capbypass/why-your-recaptcha-v3-score-is-low-3mpf</guid>
      <description>&lt;p&gt;If your reCAPTCHA v3 score comes back at 0.1 or 0.3 no matter what solver you throw at it, the solver usually isn't the problem - your IP is. The v3 score is a risk verdict Google assigns mostly from network reputation, and no fingerprint trick overrides a blacklisted datacenter range. This post explains what the score measures, which parts a solver controls, and how to stop blaming the wrong layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  what the v3 score actually is
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA v3 never shows a checkbox. It runs in the background, watches the session, and returns a float between &lt;code&gt;0.0&lt;/code&gt; and &lt;code&gt;1.0&lt;/code&gt; with every token. &lt;code&gt;1.0&lt;/code&gt; means "almost certainly human", &lt;code&gt;0.0&lt;/code&gt; means "almost certainly a bot".&lt;/p&gt;

&lt;p&gt;The score is not pass/fail. &lt;strong&gt;The site owner picks the threshold&lt;/strong&gt;, and you never see it. A login form might reject anything under &lt;code&gt;0.7&lt;/code&gt;; a low-stakes newsletter signup might accept &lt;code&gt;0.3&lt;/code&gt;. So "my score is 0.5" is meaningless until you know what the target site does with it.&lt;/p&gt;

&lt;p&gt;That also means the same token can succeed on one site and bounce on another. You're not solving a captcha - you're trying to clear a threshold you can't read.&lt;/p&gt;




&lt;h2&gt;
  
  
  why the score is mostly your IP
&lt;/h2&gt;

&lt;p&gt;Google scores the request, not just the widget. The single biggest input is the network the request comes from.&lt;/p&gt;

&lt;p&gt;Rough tiers, from what's observable in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter IPs (AWS, GCP, OVH, Hetzner ranges):&lt;/strong&gt; capped low. Often &lt;code&gt;0.1&lt;/code&gt;-&lt;code&gt;0.3&lt;/code&gt; even with a flawless device fingerprint. Google maintains these ranges and discounts them by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flagged or overused residential proxies:&lt;/strong&gt; mid. If a proxy IP has been hammered by every scraper on the same provider, its reputation is already burned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean residential / mobile IPs:&lt;/strong&gt; this is where &lt;code&gt;0.7&lt;/code&gt;+ becomes reachable. Mobile carrier IPs (rotating behind CGNAT) tend to score highest because real humans share them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why a solver that hits &lt;code&gt;0.7&lt;/code&gt; on a clean residential proxy drops to &lt;code&gt;0.2&lt;/code&gt; the moment you point it at a datacenter exit. Nothing about the client changed. The IP did.&lt;/p&gt;

&lt;p&gt;If you take one thing from this post: &lt;strong&gt;fix the proxy before you blame the solver.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  what a solver can and can't move
&lt;/h2&gt;

&lt;p&gt;A solver controls the client side of the score. CapBypass sends each token with a coherent, real-device signal profile - a consistent device fingerprint plus human-shaped timing and a matching &lt;code&gt;pageAction&lt;/code&gt;. That moves the client-derived portion of the score from "obvious automation" up to "ordinary user".&lt;/p&gt;

&lt;p&gt;What no solver can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launder a datacenter IP into a residential reputation.&lt;/li&gt;
&lt;li&gt;Raise the score above whatever ceiling Google has already assigned your network range.&lt;/li&gt;
&lt;li&gt;Change the site's threshold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the model is: &lt;strong&gt;the solver sets your ceiling, your proxy sets your floor.&lt;/strong&gt; Bring a clean IP and the signal profile can actually reach it. Bring a burned datacenter IP and you've capped yourself before the first request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju9chzudllr97ylky55y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju9chzudllr97ylky55y.webp" alt="Same solver, different IP: datacenter IPs cap around 0.1-0.3, flagged residential lands near 0.5, clean residential and mobile reach 0.7-0.9+." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  solving v3 the right way
&lt;/h2&gt;

&lt;p&gt;Because the IP is the dominant factor, you almost always want the &lt;strong&gt;proxied&lt;/strong&gt; task type for v3, not proxyless. &lt;code&gt;ReCaptchaV3Task&lt;/code&gt; requires you to pass your own proxy - and the gateway enforces it. Submit a proxied task with no &lt;code&gt;proxy&lt;/code&gt; field and you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errorId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errorCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PROXY_REQUIRED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errorDescription"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's deliberate. For v3 the proxy is part of the solve, not an afterthought. Here's the full create request with curl:&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://api.capbypass.pro/createTask &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{
    "clientKey": "YOUR_API_KEY",
    "task": {
      "type": "ReCaptchaV3Task",
      "websiteURL": "https://example.com/login",
      "websiteKey": "6Lc...your-site-key",
      "pageAction": "login",
      "proxy": "host:port:user:pass"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proxy string is &lt;code&gt;host:port:user:pass&lt;/code&gt;. &lt;code&gt;pageAction&lt;/code&gt; must match the action the page actually fires - more on that below. The response hands back a task id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errorId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"taskId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5f9b...-uuid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then poll &lt;code&gt;getTaskResult&lt;/code&gt; until &lt;code&gt;status&lt;/code&gt; flips to &lt;code&gt;ready&lt;/code&gt;:&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://api.capbypass.pro/getTaskResult &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{ "clientKey": "YOUR_API_KEY", "taskId": "5f9b...-uuid" }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"solution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"gRecaptchaResponse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"03AGdBq25..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gRecaptchaResponse&lt;/code&gt; is the token you submit to the target site exactly as a real client would - in the form field or request body the site expects.&lt;/p&gt;

&lt;p&gt;The same flow in Python with the &lt;a href="https://capbypass.pro/docs/sdks/python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capbypass&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapBypass&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapBypass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPBYPASS_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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;ReCaptchaV3Task&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;websiteURL&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://example.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;websiteKey&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;6Lc...your-site-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;pageAction&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;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;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;host:port:user:pass&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solution&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;gRecaptchaResponse&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` as g-recaptcha-response in your login request
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer TypeScript or Go? The same task works through the &lt;a href="https://capbypass.pro/docs/sdks/typescript" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; and &lt;a href="https://capbypass.pro/docs/sdks/go" rel="noopener noreferrer"&gt;Go&lt;/a&gt; SDKs.&lt;/p&gt;

&lt;p&gt;If you genuinely can't supply a proxy, &lt;code&gt;ReCaptchaV3TaskProxyLess&lt;/code&gt; routes through the CapBypass IP pool - but you inherit whatever reputation that pool's exit has for your target. For score-sensitive endpoints, a clean proxy you control beats a shared pool.&lt;/p&gt;

&lt;p&gt;For the full task schema, including the Enterprise variants, see the &lt;a href="https://capbypass.pro/docs/captcha/recaptcha-v3" rel="noopener noreferrer"&gt;reCAPTCHA v3&lt;/a&gt; docs. Both &lt;code&gt;createTask&lt;/code&gt; and &lt;code&gt;getTaskResult&lt;/code&gt; are covered in the &lt;a href="https://capbypass.pro/docs/api-reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus: +5% credits on every top-up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New to CapBypass? Apply code &lt;code&gt;WELCOME_2026&lt;/code&gt; at checkout for an extra 5% in credits on every top-up, with no minimum and no expiry. Redeem it on the &lt;a href="https://capbypass.pro/dashboard/topup" rel="noopener noreferrer"&gt;top-up page&lt;/a&gt; and run the &lt;code&gt;ReCaptchaV3Task&lt;/code&gt; flow above on us.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  debugging a score that won't go up
&lt;/h2&gt;

&lt;p&gt;Work down this list before you open a support ticket:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check the proxy class first.&lt;/strong&gt; Run your proxy through an IP-reputation lookup. If it resolves to a datacenter ASN, that's your ceiling. Swap to clean residential or mobile and re-test before changing anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match &lt;code&gt;pageAction&lt;/code&gt; to the real action.&lt;/strong&gt; v3 scores are action-scoped. If the page fires &lt;code&gt;grecaptcha.execute(siteKey, {action: 'login'})&lt;/code&gt; and you submit &lt;code&gt;pageAction: "homepage"&lt;/code&gt;, the action mismatch alone drags the score down. Read the page's JS and copy the exact string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the token once, fast.&lt;/strong&gt; v3 tokens expire after ~2 minutes and are single-use. A token sitting in a queue for 90 seconds may verify as stale. Solve, then submit immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm v3 vs Enterprise.&lt;/strong&gt; Enterprise sitekeys (loaded from &lt;code&gt;enterprise.js&lt;/code&gt; / &lt;code&gt;recaptcha/enterprise.js&lt;/code&gt;) need &lt;code&gt;ReCaptchaV3EnterpriseTask&lt;/code&gt;, not &lt;code&gt;ReCaptchaV3Task&lt;/code&gt;. Wrong family = wrong solve path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop reusing one IP across thousands of solves.&lt;/strong&gt; Even a clean residential IP degrades if you route every request through it. Rotate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've cleared all five and still floor out, the target's threshold may simply be higher than any score that network can earn - at which point the fix is a better proxy, not a better solver.&lt;/p&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is a higher score always better?&lt;/strong&gt;&lt;br&gt;
For you, yes - a higher score clears more thresholds. But you can't force an arbitrary score; you can only remove the things dragging it down. The realistic goal is "high enough for this site's threshold", not &lt;code&gt;1.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the same setup score differently on two sites?&lt;/strong&gt;&lt;br&gt;
Each site sets its own threshold and fires its own &lt;code&gt;action&lt;/code&gt;. Same token quality, different bar. Tune &lt;code&gt;pageAction&lt;/code&gt; per site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will a proxyless task get me a good v3 score?&lt;/strong&gt;&lt;br&gt;
Sometimes, but you're gambling on the pool exit's reputation for your specific target. For score-sensitive flows, supply your own clean proxy with &lt;code&gt;ReCaptchaV3Task&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the device fingerprint matter at all?&lt;/strong&gt;&lt;br&gt;
Yes - that's the half a solver controls. A broken or inconsistent fingerprint caps you low even on a great IP. CapBypass sends a coherent real-device fingerprint so the client side isn't the thing holding you back. The IP is the other half, and that half is on you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/recaptcha-v3-score-low" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>recaptcha</category>
      <category>proxy</category>
      <category>webscraping</category>
      <category>python</category>
    </item>
    <item>
      <title>Bypass AWS WAF challenges in Python</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 21:02:03 +0000</pubDate>
      <link>https://dev.to/capbypass/bypass-aws-waf-challenges-in-python-2l22</link>
      <guid>https://dev.to/capbypass/bypass-aws-waf-challenges-in-python-2l22</guid>
      <description>&lt;p&gt;AWS WAF drops a JavaScript challenge in front of a page before any real response comes back, and a plain Python request just gets the challenge HTML instead of your data. This post detects that challenge, solves it through CapBypass, and replays the returned cookie on the request - about 30 lines of Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  what aws waf blocks
&lt;/h2&gt;

&lt;p&gt;AWS WAF's challenge sits between your client and the origin. On a protected page you get an interstitial that runs &lt;code&gt;challenge.js&lt;/code&gt; (a proof-of-work token generator) and only sets the real &lt;code&gt;aws-waf-token&lt;/code&gt; cookie once the JavaScript finishes. A scraper that does not run that JS never gets the cookie, so every request loops back to the challenge.&lt;/p&gt;

&lt;p&gt;The tell is in the response: a small HTML page that loads a script from &lt;code&gt;*.token.awswaf.com&lt;/code&gt;, often with an HTTP &lt;code&gt;405&lt;/code&gt; and a &lt;code&gt;window.gokuProps&lt;/code&gt; object holding &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;iv&lt;/code&gt;, and &lt;code&gt;context&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  detecting the challenge
&lt;/h2&gt;

&lt;p&gt;Check the body for the AWS WAF markers before you trust a response:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_waf_challenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&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="n"&gt;text&lt;/span&gt;
    &lt;span class="nf"&gt;return &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.awswaf.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gokuProps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;awswaf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&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="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;server&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&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;If &lt;code&gt;is_waf_challenge&lt;/code&gt; is true, the page is gated and you need a solved cookie before retrying.&lt;/p&gt;




&lt;h2&gt;
  
  
  solving with capbypass
&lt;/h2&gt;

&lt;p&gt;Send the target URL to CapBypass with the &lt;code&gt;AntiAwsWafTaskProxyLess&lt;/code&gt; task. With auto-detection you only pass &lt;code&gt;websiteURL&lt;/code&gt; - the solver visits the page, finds &lt;code&gt;gokuProps&lt;/code&gt; and the &lt;code&gt;challenge.js&lt;/code&gt; URL, and runs the challenge for you.&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://api.capbypass.pro/createTask &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{
    "clientKey": "YOUR_API_KEY",
    "task": {
      "type": "AntiAwsWafTaskProxyLess",
      "websiteURL": "https://example.com/protected-page"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errorId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"taskId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5f9b...-uuid"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poll &lt;code&gt;getTaskResult&lt;/code&gt; until &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;ready&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ready"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"solution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cookie"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws-waf-token=xxxxxxxx..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userAgent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mozilla/5.0 ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"secChUa"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Chromium&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;v=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same flow in Python with the &lt;a href="https://capbypass.pro/docs/sdks/python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capbypass&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapBypass&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapBypass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPBYPASS_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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;AntiAwsWafTaskProxyLess&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;websiteURL&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://example.com/protected-page&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;solution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cookie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c1"&gt;# "aws-waf-token=..."
&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;solution&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;userAgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# match this on your request
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the full parameter list (manual &lt;code&gt;awsKey&lt;/code&gt; / &lt;code&gt;awsIv&lt;/code&gt; / &lt;code&gt;awsContext&lt;/code&gt; situations) see the &lt;a href="https://capbypass.pro/docs/captcha/aws-waf" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt; docs and the &lt;a href="https://capbypass.pro/docs/api-reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gdi3lrlktuiqq63t4ul.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0gdi3lrlktuiqq63t4ul.webp" alt="AWS WAF solve flow: a JS challenge blocks the request, AntiAwsWafTaskProxyLess solves it, you get an aws-waf-token cookie, and you replay the request." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  replaying the cookies
&lt;/h2&gt;

&lt;p&gt;AWS WAF binds the token to the browser identity that solved it, so set the returned cookie &lt;strong&gt;and&lt;/strong&gt; reuse the same &lt;code&gt;userAgent&lt;/code&gt; on your retry. Match them or the origin re-challenges you.&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;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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="mi"&gt;1&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;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;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;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="n"&gt;resp&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://example.com/protected-page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# resp is now the real page, not the challenge
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the session alive and the cookie rides along on every subsequent request until it expires.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus: +5% credits on every top-up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New to CapBypass? Apply code &lt;code&gt;WELCOME_2026&lt;/code&gt; at checkout for an extra 5% in credits on every top-up, with no minimum and no expiry. Redeem it on the &lt;a href="https://capbypass.pro/dashboard/topup" rel="noopener noreferrer"&gt;top-up page&lt;/a&gt; and put it toward your first &lt;code&gt;AntiAwsWafTaskProxyLess&lt;/code&gt; solves.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  things that go wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User-Agent mismatch.&lt;/strong&gt; The token is tied to the UA used during the solve. If your request UA differs from &lt;code&gt;solution.userAgent&lt;/code&gt;, AWS WAF rejects the cookie. Always copy it over (and &lt;code&gt;secChUa&lt;/code&gt; if you send client hints).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token expired.&lt;/strong&gt; &lt;code&gt;aws-waf-token&lt;/code&gt; is short-lived. Solve, replay, and finish the request promptly instead of queueing the cookie for later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong domain on the cookie.&lt;/strong&gt; Set the cookie on the exact host you are hitting, not a parent domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual CAPTCHA instead of JS challenge.&lt;/strong&gt; Some WAF configs escalate to an image CAPTCHA; the solution then carries a &lt;code&gt;captchaVoucher&lt;/code&gt; JWT rather than a plain cookie. Handle both keys.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a proxy for AWS WAF?&lt;/strong&gt;&lt;br&gt;
Not with &lt;code&gt;AntiAwsWafTaskProxyLess&lt;/code&gt; - it runs through the CapBypass pool. For IP-sensitive targets use &lt;code&gt;AntiAwsWafTask&lt;/code&gt; and pass your own proxy as &lt;code&gt;host:port:user:pass&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the page still challenge me after I set the cookie?&lt;/strong&gt;&lt;br&gt;
Almost always a User-Agent mismatch. The token is bound to the UA from the solve, so send &lt;code&gt;solution.userAgent&lt;/code&gt; on the same request that carries the cookie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long is the aws-waf-token valid?&lt;/strong&gt;&lt;br&gt;
It is short-lived (minutes). Treat it as single-session: solve, replay immediately, and re-solve when it expires rather than caching it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if there is no gokuProps on the page?&lt;/strong&gt;&lt;br&gt;
Pass just the &lt;code&gt;websiteURL&lt;/code&gt; and let auto-detection find the &lt;code&gt;challenge.js&lt;/code&gt; script. If the site hides it, supply &lt;code&gt;awsChallengeJS&lt;/code&gt; (and &lt;code&gt;awsKey&lt;/code&gt; / &lt;code&gt;awsIv&lt;/code&gt; / &lt;code&gt;awsContext&lt;/code&gt;) manually per the docs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/bypass-aws-waf-python" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webscraping</category>
      <category>security</category>
      <category>automation</category>
    </item>
    <item>
      <title>reCAPTCHA error codes explained</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 21:01:59 +0000</pubDate>
      <link>https://dev.to/capbypass/recaptcha-error-codes-explained-5aoe</link>
      <guid>https://dev.to/capbypass/recaptcha-error-codes-explained-5aoe</guid>
      <description>&lt;p&gt;When reCAPTCHA breaks, it rarely says why in plain language - you get a short code like &lt;code&gt;invalid-input-response&lt;/code&gt; or a red "ERROR for site owner" banner. This post lists the codes you actually hit when automating, what each one means, and whether it is your token or your setup at fault.&lt;/p&gt;




&lt;h2&gt;
  
  
  where these codes surface
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA errors show up in two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-side&lt;/strong&gt;, in the &lt;code&gt;error-codes&lt;/code&gt; array of the siteverify response (&lt;code&gt;https://www.google.com/recaptcha/api/siteverify&lt;/code&gt;). This is what the site you are hitting sees when it validates your token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side&lt;/strong&gt;, as a rendered banner ("ERROR for site owner: Invalid site key") or a JS console message ("Cannot contact reCAPTCHA").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You usually only control the token you submit, so the useful split is: which codes mean a bad/expired token versus a misconfigured request.&lt;/p&gt;




&lt;h2&gt;
  
  
  the common codes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;missing-input-response&lt;/code&gt;&lt;/strong&gt; - no token was sent at all. Your &lt;code&gt;g-recaptcha-response&lt;/code&gt; field is empty. Check you actually attached the solved token to the form/body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;invalid-input-response&lt;/code&gt;&lt;/strong&gt; - the token is malformed, expired, or already used. v2/v3 tokens are single-use and expire in ~2 minutes. Solve, submit immediately, never reuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timeout-or-duplicate&lt;/code&gt;&lt;/strong&gt; - the token was valid but submitted too late or twice. Same fix: one token, one request, fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;missing-input-secret&lt;/code&gt; / &lt;code&gt;invalid-input-secret&lt;/code&gt;&lt;/strong&gt; - the site's own secret key is missing or wrong. This is server-side config on the target, not something your client can fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bad-request&lt;/code&gt;&lt;/strong&gt; - the request to siteverify was malformed (wrong content type, garbled body).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;browser-error&lt;/code&gt; / "Cannot contact reCAPTCHA"&lt;/strong&gt; - the client could not reach Google. Usually a network/proxy issue on the solving side.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the client-side banners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Invalid site key"&lt;/strong&gt; - the &lt;code&gt;websiteKey&lt;/code&gt; (siteKey) is wrong for that domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Invalid domain for site key"&lt;/strong&gt; - the siteKey is restricted to a domain you are not on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Invalid key type"&lt;/strong&gt; - you are treating a v3 key as v2 or vice versa.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  your fault vs the token's fault
&lt;/h2&gt;

&lt;p&gt;Split the list before you debug:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token quality / lifecycle&lt;/strong&gt; (you can fix): &lt;code&gt;missing-input-response&lt;/code&gt;, &lt;code&gt;invalid-input-response&lt;/code&gt;, &lt;code&gt;timeout-or-duplicate&lt;/code&gt;. Get a fresh token and submit it once, fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key / domain config&lt;/strong&gt; (the target's setup, or your task params): "Invalid site key", "Invalid domain", "Invalid key type", &lt;code&gt;*-input-secret&lt;/code&gt;. Re-check the &lt;code&gt;websiteKey&lt;/code&gt; and that you picked the right reCAPTCHA version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A token that verifies but still gets you blocked is a different problem: a low v3 score. That is not an error code at all - the token is valid, the site just set a threshold you did not clear. See &lt;a href="https://capbypass.pro/blog/recaptcha-v3-score-low" rel="noopener noreferrer"&gt;why your reCAPTCHA v3 score is low&lt;/a&gt; for that.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10qw3l5zea1z7xyv7ucc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10qw3l5zea1z7xyv7ucc.webp" alt="reCAPTCHA error codes split in two columns: fixable by you (solve fresh, submit once) versus config issue (wrong site key, domain, or key type)." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  action mismatch (v3)
&lt;/h2&gt;

&lt;p&gt;On reCAPTCHA v3, an &lt;code&gt;action&lt;/code&gt; that does not match what the page expects quietly lowers your score (no error code, just a worse number). If the page calls &lt;code&gt;grecaptcha.execute(siteKey, {action: 'login'})&lt;/code&gt;, your task must send the same:&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://api.capbypass.pro/createTask &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{
    "clientKey": "YOUR_API_KEY",
    "task": {
      "type": "ReCaptchaV3Task",
      "websiteURL": "https://example.com/login",
      "websiteKey": "6Lc...your-site-key",
      "pageAction": "login",
      "proxy": "host:port:user:pass"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  solving cleanly with capbypass
&lt;/h2&gt;

&lt;p&gt;A fresh, correctly-scoped token avoids the whole token-fault half of the list. Solve, read &lt;code&gt;gRecaptchaResponse&lt;/code&gt;, submit it once:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capbypass&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapBypass&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapBypass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPBYPASS_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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;ReCaptchaV2Task&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;websiteURL&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://example.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;websiteKey&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;6Lc...your-site-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;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;host:port:user:pass&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solution&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;gRecaptchaResponse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# attach as g-recaptcha-response, submit right away
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CapBypass surfaces its own task-level problems through the &lt;code&gt;errorCode&lt;/code&gt; field on the API response (for example &lt;code&gt;ERROR_CAPTCHA_UNSOLVABLE&lt;/code&gt;, &lt;code&gt;ERROR_ZERO_BALANCE&lt;/code&gt;, &lt;code&gt;ERROR_INVALID_TASK_DATA&lt;/code&gt;) - distinct from Google's verify codes above. Full list in the &lt;a href="https://capbypass.pro/docs/api-reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;; task schemas in the &lt;a href="https://capbypass.pro/docs/captcha/recaptcha-v2" rel="noopener noreferrer"&gt;reCAPTCHA v2&lt;/a&gt; and &lt;a href="https://capbypass.pro/docs/captcha/recaptcha-v3" rel="noopener noreferrer"&gt;reCAPTCHA v3&lt;/a&gt; docs.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus: +5% credits on every top-up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New to CapBypass? Apply code &lt;code&gt;WELCOME_2026&lt;/code&gt; at checkout for an extra 5% in credits on every top-up, with no minimum and no expiry. Redeem it on the &lt;a href="https://capbypass.pro/dashboard/topup" rel="noopener noreferrer"&gt;top-up page&lt;/a&gt; and put it toward your first &lt;code&gt;ReCaptchaV2Task&lt;/code&gt; solves.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What does &lt;code&gt;invalid-input-response&lt;/code&gt; mean?&lt;/strong&gt;&lt;br&gt;
The token you submitted is malformed, expired, or already used. reCAPTCHA tokens are single-use and expire in about 2 minutes - solve and submit in one quick step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is &lt;code&gt;timeout-or-duplicate&lt;/code&gt; my fault?&lt;/strong&gt;&lt;br&gt;
Yes, usually. The token was fine but arrived too late or twice. Use each token exactly once and submit it immediately after solving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do I see "Invalid site key"?&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;websiteKey&lt;/code&gt; you passed does not match the one the page uses, or it is restricted to another domain. Re-read the siteKey from the live page and confirm the domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A token verifies but I still get blocked. Why?&lt;/strong&gt;&lt;br&gt;
That is not an error code - it is a low v3 score. The token is valid; the site's threshold is higher than the score your request earned. Improve the request (clean IP, matching action) rather than the token.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/recaptcha-error-codes" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>recaptcha</category>
      <category>webscraping</category>
      <category>automation</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Add captcha solving to a Python scraper</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 21:01:55 +0000</pubDate>
      <link>https://dev.to/capbypass/add-captcha-solving-to-a-python-scraper-ipm</link>
      <guid>https://dev.to/capbypass/add-captcha-solving-to-a-python-scraper-ipm</guid>
      <description>&lt;p&gt;Your &lt;code&gt;requests&lt;/code&gt; scraper runs fine until a target drops a reCAPTCHA on the login or search page, and then every response is the challenge instead of the data. This post wires CapBypass into a plain Python scraper: detect the captcha, solve it through the API, inject the token, and retry - no browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  when your scraper hits a captcha
&lt;/h2&gt;

&lt;p&gt;A captcha shows up as a page that is not your data: a reCAPTCHA widget (&lt;code&gt;g-recaptcha&lt;/code&gt; div, a &lt;code&gt;grecaptcha.execute&lt;/code&gt; call), an hCaptcha frame, or an AWS WAF challenge. With &lt;code&gt;requests&lt;/code&gt;/&lt;code&gt;httpx&lt;/code&gt; you cannot run the widget's JavaScript, so the form never gets a valid &lt;code&gt;g-recaptcha-response&lt;/code&gt; and the server rejects the submit.&lt;/p&gt;

&lt;p&gt;The fix is to get that token from a solving API and submit it yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  detecting it
&lt;/h2&gt;

&lt;p&gt;Recognise the challenge deterministically before you waste a 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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;needs_recaptcha&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grecaptcha.execute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;site_key&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&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="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="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;needs_recaptcha&lt;/code&gt; is true, pull the &lt;code&gt;data-sitekey&lt;/code&gt; and solve before submitting.&lt;/p&gt;




&lt;h2&gt;
  
  
  solving via capbypass
&lt;/h2&gt;

&lt;p&gt;Send the site URL and key to CapBypass and read the token back. Use the &lt;a href="https://capbypass.pro/docs/sdks/python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capbypass&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapBypass&lt;/span&gt;

&lt;span class="n"&gt;solver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapBypass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPBYPASS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;solve_recaptcha_v2&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;ReCaptchaV2Task&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;websiteURL&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;websiteKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&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;host:port:user:pass&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solution&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;gRecaptchaResponse&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;&lt;code&gt;solve()&lt;/code&gt; handles the create-task / poll loop for you and returns once the token is ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  injecting the token and retrying
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA tokens go into the form field named &lt;code&gt;g-recaptcha-response&lt;/code&gt;. Add it to the POST body you were already sending:&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;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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&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;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="n"&gt;url&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;payload&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;username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;username&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="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;needs_recaptcha&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;solve_recaptcha_v2&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="nf"&gt;site_key&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;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha-response&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;token&lt;/span&gt;   &lt;span class="c1"&gt;# submit immediately
&lt;/span&gt;
    &lt;span class="k"&gt;return&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;url&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;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For challenge-based protection like &lt;a href="https://capbypass.pro/docs/captcha/aws-waf" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt; the solution is a cookie, not a form field - set it on &lt;code&gt;session.cookies&lt;/code&gt; and reuse the returned &lt;code&gt;userAgent&lt;/code&gt; instead of adding a body field.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhm9l8kpnx2fpxpgf0ss.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhm9l8kpnx2fpxpgf0ss.webp" alt="POST /login form payload with the g-recaptcha-response field added in emerald, showing where the solved token slots into your request body." width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  a retry pattern
&lt;/h2&gt;

&lt;p&gt;Tokens are single-use and expire fast, so solve as late as possible (right before the submit) and retry once on a captcha response rather than reusing a stale token:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_with_retry&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;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;):&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;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;url&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;payload&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="nf"&gt;needs_recaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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;resp&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha-response&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="nf"&gt;solve_recaptcha_v2&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="nf"&gt;site_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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;resp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus: +5% credits on every top-up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New to CapBypass? Apply code &lt;code&gt;WELCOME_2026&lt;/code&gt; at checkout for an extra 5% in credits on every top-up, with no minimum and no expiry. Redeem it on the &lt;a href="https://capbypass.pro/dashboard/topup" rel="noopener noreferrer"&gt;top-up page&lt;/a&gt; and put it toward your first &lt;code&gt;ReCaptchaV2Task&lt;/code&gt; solves.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  things that go wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stale token.&lt;/strong&gt; Solving at the top of the run and submitting minutes later fails with &lt;code&gt;timeout-or-duplicate&lt;/code&gt;. Solve right before the submit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong site key.&lt;/strong&gt; Read &lt;code&gt;data-sitekey&lt;/code&gt; from the live page each run; do not hardcode it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;v3 instead of v2.&lt;/strong&gt; If the page uses &lt;code&gt;grecaptcha.execute(...)&lt;/code&gt; with an action, it is reCAPTCHA v3 - switch to &lt;code&gt;ReCaptchaV3Task&lt;/code&gt; and pass the matching &lt;code&gt;pageAction&lt;/code&gt;. See the &lt;a href="https://capbypass.pro/docs/captcha/recaptcha-v3" rel="noopener noreferrer"&gt;reCAPTCHA v3&lt;/a&gt; docs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this work with httpx instead of requests?&lt;/strong&gt;&lt;br&gt;
Yes. The flow is identical - swap &lt;code&gt;requests.Session()&lt;/code&gt; for &lt;code&gt;httpx.Client()&lt;/code&gt;; you still inject &lt;code&gt;g-recaptcha-response&lt;/code&gt; into the POST data or set the cookie on the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a real browser?&lt;/strong&gt;&lt;br&gt;
No. The solving API runs the challenge for you; your scraper stays a plain HTTP client and just submits the returned token or cookie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I put the token?&lt;/strong&gt;&lt;br&gt;
In the form field &lt;code&gt;g-recaptcha-response&lt;/code&gt; for reCAPTCHA/hCaptcha. For AWS WAF set the returned cookie on your session instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I avoid solving on every request?&lt;/strong&gt;&lt;br&gt;
Only solve when &lt;code&gt;needs_recaptcha&lt;/code&gt; is true. Most requests in a session will not be challenged once you are past the gated page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/captcha-solving-python-scraper" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>scraping</category>
      <category>webscraping</category>
      <category>captcha</category>
    </item>
    <item>
      <title>Captcha solving with Playwright</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 21:01:51 +0000</pubDate>
      <link>https://dev.to/capbypass/captcha-solving-with-playwright-1o8</link>
      <guid>https://dev.to/capbypass/captcha-solving-with-playwright-1o8</guid>
      <description>&lt;p&gt;Playwright drives a real browser, so people expect captchas to just work - but reCAPTCHA and hCaptcha still score the session and block automated traffic. This post keeps your Playwright flow and offloads only the captcha: detect it on the page, get a token from CapBypass over HTTP, inject it, and submit.&lt;/p&gt;




&lt;h2&gt;
  
  
  why playwright still hits captchas
&lt;/h2&gt;

&lt;p&gt;A real browser clears the "can it run JavaScript" bar, but reCAPTCHA v3 and hCaptcha go further - they score behaviour and device signals. Automated Playwright sessions often score low enough to be challenged or silently rejected. You still end up needing a token the site will accept.&lt;/p&gt;

&lt;p&gt;Rather than fight the in-page widget, solve it out of band and drop the token into the page Playwright already controls.&lt;/p&gt;




&lt;h2&gt;
  
  
  detecting the challenge on the page
&lt;/h2&gt;

&lt;p&gt;Read the site key straight out of the DOM:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_recaptcha_key&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;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;query_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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;el&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a key comes back, the page is gated and you solve before submitting.&lt;/p&gt;




&lt;h2&gt;
  
  
  solving via the api
&lt;/h2&gt;

&lt;p&gt;Your Playwright script calls the CapBypass HTTP API for the token - no second browser, just a request. Use the &lt;a href="https://capbypass.pro/docs/sdks/python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capbypass&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapBypass&lt;/span&gt;

&lt;span class="n"&gt;solver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapBypass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPBYPASS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;solve&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;ReCaptchaV2Task&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;websiteURL&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;websiteKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&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;host:port:user:pass&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solution&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;gRecaptchaResponse&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;h2&gt;
  
  
  injecting the token into the page
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA reads its answer from a hidden &lt;code&gt;&amp;lt;textarea name="g-recaptcha-response"&amp;gt;&lt;/code&gt;. Set it via &lt;code&gt;page.evaluate&lt;/code&gt;, then submit the form as a user would:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pass_recaptcha&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;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&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;get_recaptcha_key&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;solve&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;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&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;(t) =&amp;gt; {
            let ta = document.querySelector(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;textarea[name=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha-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 (!ta) {
                ta = document.createElement(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;textarea&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;);
                ta.name = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha-response&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;;
                ta.style.display = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;;
                document.body.appendChild(ta);
            }
            ta.value = t;
        }&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="k"&gt;await&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=submit]&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;For an invisible or callback-driven widget you may need to call the site's JS callback with the token instead of clicking submit - inspect what &lt;code&gt;grecaptcha.render&lt;/code&gt; registered.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbadnow5ubqq9qcdhluyn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbadnow5ubqq9qcdhluyn.webp" alt="Playwright token injection pipeline: solve via CapBypass HTTP, then page.evaluate sets textarea[name=g-recaptcha-response].value, then page.click submits the form with a valid token." width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  hcaptcha and v3
&lt;/h2&gt;

&lt;p&gt;Same shape, different field/task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;hCaptcha&lt;/strong&gt;: the token goes into &lt;code&gt;textarea[name="h-captcha-response"]&lt;/code&gt;; solve with &lt;code&gt;HCaptchaTaskProxyless&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reCAPTCHA v3&lt;/strong&gt;: there is no checkbox - the page calls &lt;code&gt;grecaptcha.execute(key, {action})&lt;/code&gt;. Solve with &lt;code&gt;ReCaptchaV3Task&lt;/code&gt; and the matching &lt;code&gt;pageAction&lt;/code&gt;, then feed the token wherever the page sends it. See the &lt;a href="https://capbypass.pro/docs/captcha/recaptcha-v3" rel="noopener noreferrer"&gt;reCAPTCHA v3&lt;/a&gt; docs.&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus: +5% credits on every top-up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;New to CapBypass? Apply code &lt;code&gt;WELCOME_2026&lt;/code&gt; at checkout for an extra 5% in credits on every top-up, with no minimum and no expiry. Redeem it on the &lt;a href="https://capbypass.pro/dashboard/topup" rel="noopener noreferrer"&gt;top-up page&lt;/a&gt; and put it toward your first &lt;code&gt;ReCaptchaV2Task&lt;/code&gt; solves.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  things that go wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token set but nothing happens.&lt;/strong&gt; The widget uses a JS callback, not a plain submit. Call the registered callback with the token instead of clicking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low v3 score even from a browser.&lt;/strong&gt; Playwright sessions can score low; a clean proxy on the solve helps. See &lt;a href="https://capbypass.pro/blog/recaptcha-v3-score-low" rel="noopener noreferrer"&gt;why your reCAPTCHA v3 score is low&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale token.&lt;/strong&gt; Solve right before injecting; tokens expire in about 2 minutes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does CapBypass run the browser for me?&lt;/strong&gt;&lt;br&gt;
No - you run Playwright. CapBypass solves the captcha over HTTP and returns a token; you inject it into your own page. It is a plain API call from inside your script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I put the token in Playwright?&lt;/strong&gt;&lt;br&gt;
For reCAPTCHA, into &lt;code&gt;textarea[name="g-recaptcha-response"]&lt;/code&gt; via &lt;code&gt;page.evaluate&lt;/code&gt;, then submit. For hCaptcha use &lt;code&gt;h-captcha-response&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is my session still blocked with a valid token?&lt;/strong&gt;&lt;br&gt;
On reCAPTCHA v3 a valid token can still score below the site's threshold. Solve through a clean proxy and match the &lt;code&gt;pageAction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playwright or just the HTTP API?&lt;/strong&gt;&lt;br&gt;
If the site works without a browser, the plain HTTP approach is cheaper and faster. Use Playwright only when the page genuinely needs a rendered session for the rest of the flow.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/captcha-solving-playwright" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>playwright</category>
      <category>captcha</category>
    </item>
    <item>
      <title>Captcha solving with Scrapy</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 20:01:08 +0000</pubDate>
      <link>https://dev.to/capbypass/captcha-solving-with-scrapy-32n9</link>
      <guid>https://dev.to/capbypass/captcha-solving-with-scrapy-32n9</guid>
      <description>&lt;p&gt;A Scrapy spider hums along until a target gates a page behind reCAPTCHA, and then your parse callbacks get challenge HTML instead of items. This post adds a downloader middleware that detects the captcha, solves it through CapBypass, and retries the request with the token - so your spiders stay clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  where scrapy hits captchas
&lt;/h2&gt;

&lt;p&gt;Scrapy issues plain HTTP requests, so any page protected by reCAPTCHA, hCaptcha, or AWS WAF comes back as the challenge, not your data. You do not want captcha logic scattered across every callback - the right place is a downloader middleware that inspects responses and re-issues the blocked ones once solved.&lt;/p&gt;




&lt;h2&gt;
  
  
  detecting in a middleware
&lt;/h2&gt;

&lt;p&gt;A middleware's &lt;code&gt;process_response&lt;/code&gt; sees every response. Flag the challenge and pull the site key:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_recaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grecaptcha.execute&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;site_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  solving via capbypass
&lt;/h2&gt;

&lt;p&gt;Wrap the solve in a small helper so the middleware stays readable. Use the &lt;a href="https://capbypass.pro/docs/sdks/python" rel="noopener noreferrer"&gt;Python SDK&lt;/a&gt;:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capbypass&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapBypass&lt;/span&gt;

&lt;span class="n"&gt;_solver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapBypass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPBYPASS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_solver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;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;ReCaptchaV2Task&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;websiteURL&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;websiteKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&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;host:port:user:pass&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solution&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;gRecaptchaResponse&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;h2&gt;
  
  
  the downloader middleware
&lt;/h2&gt;

&lt;p&gt;Detect, solve, and re-issue the request with the token in the form body. Return the new request from &lt;code&gt;process_response&lt;/code&gt; so Scrapy reschedules it:&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;scrapy.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FormRequest&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CaptchaMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&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="nf"&gt;is_recaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;response&lt;/span&gt;

        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;site_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&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;key&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&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;captcha_retried&lt;/span&gt;&lt;span class="sh"&gt;"&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;response&lt;/span&gt;  &lt;span class="c1"&gt;# give up rather than loop forever
&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="n"&gt;response&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;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;FormRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&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;formdata&lt;/span&gt;&lt;span class="o"&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&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;formdata&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha-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="n"&gt;meta&lt;/span&gt;&lt;span class="o"&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;captcha_retried&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;dont_filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callback&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;Enable it in &lt;code&gt;settings.py&lt;/code&gt;:&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="n"&gt;DOWNLOADER_MIDDLEWARES&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;myproject.middlewares.CaptchaMiddleware&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;585&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;The &lt;code&gt;captcha_retried&lt;/code&gt; flag stops an infinite solve loop if the token is rejected; &lt;code&gt;dont_filter=True&lt;/code&gt; lets the same URL through the dedupe filter on retry.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkr8yh4bqrgn2s1qs6g0k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkr8yh4bqrgn2s1qs6g0k.webp" alt="Scrapy pipeline: Spider, Engine, Downloader Middlewares (with CaptchaMiddleware highlighted) and Downloader; a dashed loop shows that on a captcha response the middleware solves and re-issues the request." width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  things that go wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infinite retry loop.&lt;/strong&gt; Without a &lt;code&gt;captcha_retried&lt;/code&gt; flag a rejected token re-triggers the solve forever. Cap it at one retry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocking call in the reactor.&lt;/strong&gt; &lt;code&gt;solve()&lt;/code&gt; is synchronous; for high concurrency run it in a thread pool or use the async API so you do not stall Scrapy's event loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS WAF, not reCAPTCHA.&lt;/strong&gt; Challenge-based protection returns a cookie, not a form token - set it on the request cookies and reuse the &lt;code&gt;userAgent&lt;/code&gt;. See the &lt;a href="https://capbypass.pro/docs/captcha/aws-waf" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt; docs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why a middleware instead of solving in the callback?&lt;/strong&gt;&lt;br&gt;
A downloader middleware sees every response in one place, so you detect and retry captchas centrally instead of repeating the logic in each spider callback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does &lt;code&gt;solve()&lt;/code&gt; block the Scrapy reactor?&lt;/strong&gt;&lt;br&gt;
The synchronous &lt;code&gt;solve()&lt;/code&gt; does. For concurrent spiders, offload it to a thread pool or use the async create-task / poll calls so the reactor keeps running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I stop infinite retries?&lt;/strong&gt;&lt;br&gt;
Set a flag in &lt;code&gt;request.meta&lt;/code&gt; (e.g. &lt;code&gt;captcha_retried&lt;/code&gt;) and bail if it is already set, so a rejected token does not loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this handle hCaptcha too?&lt;/strong&gt;&lt;br&gt;
Yes - detect the hCaptcha frame, solve with &lt;code&gt;HCaptchaTaskProxyless&lt;/code&gt;, and inject the token into &lt;code&gt;h-captcha-response&lt;/code&gt; instead of &lt;code&gt;g-recaptcha-response&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/captcha-solving-scrapy" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>scraping</category>
      <category>webscraping</category>
      <category>captcha</category>
    </item>
    <item>
      <title>How a captcha-solving API works</title>
      <dc:creator>Bob</dc:creator>
      <pubDate>Sun, 14 Jun 2026 19:56:43 +0000</pubDate>
      <link>https://dev.to/capbypass/how-a-captcha-solving-api-works-2ac8</link>
      <guid>https://dev.to/capbypass/how-a-captcha-solving-api-works-2ac8</guid>
      <description>&lt;p&gt;A captcha-solving API turns "a human has to click this" into a normal HTTP call: you describe the challenge, the service solves it, and you get back a token or cookie you submit like a real browser would. This post walks the model end to end so you know what you are actually paying for.&lt;/p&gt;




&lt;h2&gt;
  
  
  why captchas block automation
&lt;/h2&gt;

&lt;p&gt;reCAPTCHA, hCaptcha, AWS WAF, and similar systems exist to tell a script apart from a person. They run JavaScript, score behaviour, and hand the page a token only once they are satisfied. A plain HTTP client never runs that JavaScript, so it never earns the token - and the protected action (login, checkout, search) stays closed.&lt;/p&gt;

&lt;p&gt;A solving API closes that gap without you driving a full browser yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  the create-task / poll / token model
&lt;/h2&gt;

&lt;p&gt;Almost every solving API (CapBypass included) uses the same two-step async shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;createTask&lt;/code&gt;&lt;/strong&gt; - you POST a description of the challenge (type, site URL, site key). You get a &lt;code&gt;taskId&lt;/code&gt; back immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;getTaskResult&lt;/code&gt;&lt;/strong&gt; - you poll that &lt;code&gt;taskId&lt;/code&gt; until &lt;code&gt;status&lt;/code&gt; flips to &lt;code&gt;ready&lt;/code&gt;, then read the &lt;code&gt;solution&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. create&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.capbypass.pro/createTask &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{ "clientKey": "YOUR_API_KEY",
        "task": { "type": "ReCaptchaV2Task",
                  "websiteURL": "https://example.com/login",
                  "websiteKey": "6Lc...",
                  "proxy": "host:port:user:pass" } }'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; { "errorId": 0, "taskId": "5f9b...-uuid" }&lt;/span&gt;

&lt;span class="c"&gt;# 2. poll&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://api.capbypass.pro/getTaskResult &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{ "clientKey": "YOUR_API_KEY", "taskId": "5f9b...-uuid" }'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; { "status": "ready", "solution": { "gRecaptchaResponse": "03AGdBq25..." } }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is async because solving takes a few seconds, and polling keeps your code simple: fire, wait, read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fx9lftl9muchivbi5st.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fx9lftl9muchivbi5st.webp" alt="How a captcha-solving API works: createTask returns a taskId, getTaskResult is polled until ready, then you read the token or cookie." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  what happens server-side
&lt;/h2&gt;

&lt;p&gt;While you poll, the service does the work you would otherwise have to: it runs the challenge's JavaScript, presents a coherent device fingerprint, routes through an appropriate IP, and produces a token the target will accept. Good APIs also hand back the &lt;code&gt;userAgent&lt;/code&gt; (and client-hint headers) they used, so you can match them on your own request and keep the identity consistent.&lt;/p&gt;

&lt;p&gt;You never see any of that machinery - you see a &lt;code&gt;taskId&lt;/code&gt; and, a few seconds later, a &lt;code&gt;solution&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  token captchas vs cookie captchas
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;solution&lt;/code&gt; shape depends on the challenge family:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token-based&lt;/strong&gt; (reCAPTCHA, hCaptcha): you get a token (&lt;code&gt;gRecaptchaResponse&lt;/code&gt;) that you inject into the form field or request body the site checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cookie / challenge-based&lt;/strong&gt; (AWS WAF, Cloudflare-style): you get a cookie string (e.g. &lt;code&gt;aws-waf-token=...&lt;/code&gt;) that you set on your HTTP session before retrying the original request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same flow, different thing to replay. The &lt;a href="https://capbypass.pro/docs/api-reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; lists every solution field per task type.&lt;/p&gt;




&lt;h2&gt;
  
  
  what you pay for
&lt;/h2&gt;

&lt;p&gt;Solving APIs bill per solved task, not per month - a few hundredths of a cent to a few cents depending on the captcha type. You pay only when a task returns &lt;code&gt;ready&lt;/code&gt; with a usable solution, which is why the model scales cleanly from a handful of solves to millions. New accounts start with the &lt;a href="https://capbypass.pro/docs/getting-started" rel="noopener noreferrer"&gt;getting-started&lt;/a&gt; flow and a small balance.&lt;/p&gt;




&lt;h2&gt;
  
  
  faq
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is it just OCR?&lt;/strong&gt;&lt;br&gt;
No. Modern captchas (reCAPTCHA v3, AWS WAF) are not images to read - they are JavaScript challenges and behavioural scores. A solving API runs the challenge and returns a token, which is very different from reading text off a picture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does a solve take?&lt;/strong&gt;&lt;br&gt;
Typically a few seconds. That is why the API is async: you create a task, poll &lt;code&gt;getTaskResult&lt;/code&gt;, and read the solution when &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;ready&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I still need a proxy?&lt;/strong&gt;&lt;br&gt;
For IP-sensitive captchas (reCAPTCHA v3, AWS WAF on strict targets), yes - you pass your own proxy so the solve happens from a clean IP. Proxyless task variants use a shared pool and suit lower-stakes targets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do I do with the result?&lt;/strong&gt;&lt;br&gt;
Token captchas: inject the token into the form/body. Cookie captchas: set the returned cookie on your HTTP session, then replay the original request.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://capbypass.pro/blog/how-captcha-solving-api-works" rel="noopener noreferrer"&gt;capbypass.pro&lt;/a&gt;. CapBypass is an &lt;a href="https://capbypass.pro" rel="noopener noreferrer"&gt;AI-powered captcha-solving API&lt;/a&gt; for reCAPTCHA, hCaptcha, Cloudflare Turnstile and AWS WAF.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>scraping</category>
      <category>automation</category>
      <category>python</category>
    </item>
  </channel>
</rss>
