Back to blog

Kasada Bypass: How to Get Past Kasada Anti-bot Protection in 2026

Share article:

Kasada is a bot-mitigation service protecting ticketing, limited-release retail, and financial platforms. Unlike Cloudflare, Akamai, or HUMAN, it shows no CAPTCHA and runs a proof-of-work challenge that a real browser solves by loading the page. A Kasada bypass, therefore, starts with a browser, not an HTTP client. This guide covers detection, the 5 layers, the tools, and the DIY limit.

How to Get Past Kasada Anti-bot Protection

TL;DR

  • Kasada uses 5 detection layers: TLS and HTTP/2 fingerprinting, IP reputation, a proof-of-work challenge, browser fingerprinting, and behavioral scoring
  • The proof-of-work runs as rotating, obfuscated JavaScript, so a real browser clears it, while a reimplemented solver targets a bundle that's rebuilt on every request
  • You can identify Kasada from the x-kpsdk- response headers and a script path under /149e9513-…, together with a plain 403 or 429 and no CAPTCHA

Kasada detection mechanisms

You can confirm Kasada on a website before writing any extraction code. Send a normal request to the target, then read the response headers and the page source for a few specific signals. One blocked response carries all of them. The cookie name is masked below because the customer chooses it and it differs between deployments:

HTTP/1.1 429 Too Many Requests
Content-Type: text/html; charset=utf-8
Content-Length: 685
x-kpsdk-ct: 03pGUrFWlK9XfKgfmPFlUzNXW2ghm0NLDmE6dtTvGbxGo0t2t5zIVI4g…
x-kpsdk-r: 1-AA
access-control-expose-headers: x-kpsdk-ct,x-kpsdk-r,x-kpsdk-c,x-kpsdk-h,x-kpsdk-fc
Set-Cookie: <customer-chosen name>=03pGUrFWlK9XfKgfmPFlUzNXW2gh…; HttpOnly; Secure
<!DOCTYPE html><html><head></head><body>
<script>window.KPSDK={};KPSDK.now=;KPSDK.start=KPSDK.now();</script>
<script src="/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/ips.js?…"></script>
</body></html>

The script URL is the first signal. Kasada serves its client from a path built on 2 UUIDs and ending in ips.js, so the full path reads /149e9513-…/2d206a39-…/ips.js. Both segments match on a second, unrelated live deployment, and the first segment matches on a third deployment. So the second UUID looks like a shared version identifier rather than a per-site value.

The headers are the second signal. The challenge response returns x-kpsdk-ct (the session token) and x-kpsdk-r (the difficulty), while the client sends x-kpsdk-cd (the per-request proof) on its own requests. Kasada also sets a session cookie, but the name is customer-chosen and differs between deployments – KP_UIDz and tkrm_alpekz are 2 examples.

The block behavior is the last signal. A protected endpoint responds to the first request with a 429, then loads after the challenge clears. When Kasada flags a session as automation, it returns a plain 403 or 429. There's no visible CAPTCHA and no challenge page.

A short script automates the check, reading those signals from the response headers, cookies, and page source.

# detect_kasada.py
# Checks a URL for Kasada anti-bot signals.
# pip install requests
import requests
KASADA_UUID = "149e9513-01fa-4fb0-aad4-566afd725d1b"
# The cookie name is customer-chosen, so this list isn't exhaustive.
KASADA_COOKIES = ("KP_UIDz", "tkrm_alpekz")
BROWSER_UA = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0.0.0 Safari/537.36")
def detect_kasada(url):
r = requests.get(url, headers={"User-Agent": BROWSER_UA}, timeout=15)
resp_headers = [k.lower() for k in r.headers]
cookies = [c.name for c in r.cookies]
signals = {
# Strong signal: this first UUID held on all 3 deployments we checked.
"kasada_script": KASADA_UUID in r.text,
# The server returns these on the challenge response.
"xkpsdk_resp_headers": [h for h in resp_headers
if h.startswith("x-kpsdk-")],
# Cookie name varies (KP_UIDz on some sites, tkrm_alpekz on others).
"kasada_cookie": [n for n in cookies if n.startswith(KASADA_COOKIES)],
"clean_block": r.status_code in (403, 429),
}
is_kasada = (signals["kasada_script"]
or bool(signals["xkpsdk_resp_headers"]))
return is_kasada, signals
if __name__ == "__main__":
target = "https://www.example.com/" # replace with your target
kasada, signals = detect_kasada(target)
print(f"Kasada detected: {kasada}")
for name, hit in signals.items():
print(f" {name}: {hit}")

Run it with python detect_kasada.py. On a Kasada-protected site, you'll see:

Kasada detected: True
kasada_script: True
xkpsdk_resp_headers: ['x-kpsdk-ct', 'x-kpsdk-r']
kasada_cookie: ['tkrm_alpekz_s1.3', 'tkrm_alpekz_s1.3-ssn']
clean_block: True

The x-kpsdk- response headers are the signal to trust. That prefix is part of Kasada's protocol and stays valid even if the specific script UUID ever rotates. The script path is a strong backup signal – its first UUID held on all 3 deployments we checked – but don't trust it alone. Don't rely on the cookie name. The output above shows a suffixed variant, tkrm_alpekz_s1.3, that a fixed-name check would miss entirely.

How Kasada's detection works, layer by layer

Kasada evaluates a request in sequence, and each layer feeds a trust score. Knowing the order tells you which fix applies to which failure.

Layer 1: TLS and HTTP/2 fingerprinting 

Before any application logic runs, Kasada inspects the TLS handshake and the HTTP/2 frame ordering. The JA3 or JA4 signature comes from the cipher and extension order. A raw HTTP/1.1 client or a default Python TLS stack produces a signature that no real Chrome or Firefox sends.

Layer 2: IP reputation 

Kasada scores the source IP. Datacenter ranges start with negative trust, while residential and mobile IPs start positive. Kasada also markets cross-network threat intelligence, so an IP already flagged as automation on another Kasada site can carry that risk into your session.

Layer 3: The proof-of-work challenge

This is the layer that makes Kasada different. The client script hands the browser a computational challenge, the browser solves it, and the result becomes a signed token attached to later requests. The challenge is cheap for one real browser and expensive to run at scale.

The challenge runs in JavaScript, not WebAssembly. Public disassembly work on ips.js describes a virtual-machine-style interpreter, and the live bundle matches that: grep it, and there are no plain text strings to read, because it builds them at runtime. The bundle is heavily obfuscated and exposes a window.KPSDK global and drifts in size. Fetches across a single day ranged from roughly 545 to 650 KB uncompressed. Your network tab will show about half that, since it reports the gzipped transfer. Kasada rotates that bundle on every request. Fetch the same URL 3 times, and you get 3 different builds:

$ for i in 1 2 3; do curl -s "https://<target>/149e9513-…/2d206a39-…/ips.js" | wc -c; done
569557
546916
572744

You can watch the handshake in any network log. It runs in 3 steps. First, the request to a protected path returns a 429 that points to ips.js. Then the browser loads that script, and the script POSTs the signals it collected to a /tl endpoint on the same 2-UUID path. That /tl call returns the clearance, and the retried request uses it.

The same 3 steps in a browser's network log – the 429, the challenge script, and the /tl call. The host is redacted.

The handshake is also a debugging signal. Once you see the /tl POST return 200, the challenge is running. So, a block after that point is the fingerprint or behavioral layers rejecting you, not layer 1. On a first page load, the layer rejecting you is the fingerprint layer because nothing has behaved yet.

Layer 4: Browser and JavaScript fingerprinting 

While the challenge runs, the same script reads canvas and WebGL output, navigator properties, screen dimensions, and the machine's timing entropy. This is the layer that separates the Chromium tools tested here.

Layer 5: Behavioral analysis 

After the first page, Kasada scores how the session behaves – pointer movement, scroll velocity, typing rhythm, dwell time, and navigation patterns. A 2026 UC Davis study of AI browsing agents found that browser fingerprints shared across automated agents have limited power to tell those agents apart, while behavioral signals separate the agents reliably. You can match every static fingerprint and still score poorly here, because the motion and timing don't look human. In practice, though, 3 of the 4 Chromium tools we ran headless never reached this layer. Kasada re-challenged them on the first page load, before there was any behavior to score.

Cloudflare, Akamai, Imperva, and HUMAN check similar signals, but Kasada is harder to keep up with. Its proof-of-work layer and short-lived token mean you resolve continuously instead of relying on a longer session cookie. If you've handled those systems, how to bypass Cloudflare and how to bypass PerimeterX show the contrast.

Before you build a bypass, check for a lighter path

The cheapest Kasada bypass often clears the challenge once, not on every request. Many sites serve the same data through a second, lighter-protected path: a commerce platform's own API host, the endpoint a mobile app talks to, or an internal JSON route the front end calls. You can find that route by capturing the page's fetch and XHR requests. An unauthenticated request to that route returns a plain 401 rather than a Kasada challenge. That's because the challenge protects the page that issues the app's auth token, not the data behind it.

Clear Kasada once in a real browser, capture the auth token the app returns, and replay it against the lighter endpoint without re-solving per request, using a TLS-impersonating client like curl_cffi. That token is the app's own – separate from Kasada's per-request clearance and longer-lived (often a ~30-minute TTL). Within that window, solving once can cover a full scrape.

The check costs one DevTools session. Mobile-app endpoints are often the lightest of those 3 routes, since a browser-based challenge is harder to serve to app traffic. If the network tab shows only same-host calls behind the same challenge, there's no door the browser can see, and you can go straight to the layers.

Kasada challenge bypass techniques

When there's no lighter door, you clear the layers directly. Each layer needs its own fix. Match the technique to the layer instead of trusting a single tool, because skipping one layer wastes the fixes you applied to the others.

Match the TLS and HTTP/2 fingerprint 

A TLS-impersonating client like curl_cffi makes your handshake look like a real browser's, which is what layer 1 inspects. That client still isn't enough, because it can't run the JavaScript that produces the token. The fingerprint gap between a plain client and an impersonating one is measurable against a public fingerprint service:

Client

Protocol

TLS (JA3)

HTTP/2 fingerprint

Plain Python Requests

HTTP/1.1

Python (c1020cdb…)

none

curl_cffi impersonating Chrome

HTTP/2

Chrome (00edbbfd…)

Akamai (52d84b11…)

Plain Requests sends a Python signature over HTTP/1.1 with no HTTP/2 fingerprint, which Kasada scores against you at layer 1. curl_cffi reproduces a Chrome signature and a real Akamai HTTP/2 fingerprint, which layer 1 checks for. Here, Akamai is the name of the fingerprint format, not the anti-bot vendor. The exact JA3 string is version-specific – it changes with the client and TLS library – so the table shows Python-class vs. Chrome-class signatures rather than fixed hashes.

Run the proof-of-work in a real browser

A real or patched browser produces the token as a side effect of loading the page, so there's no solve step to call. A bare JavaScript engine won't substitute for a browser, because the challenge reads canvas, WebGL, and navigator values that only a browser has.

The alternative is to reimplement the challenge as a standalone Python solver. The live bundle is heavily obfuscated and runs as a virtual-machine-style interpreter, with no plaintext fingerprinting or obvious network calls to read, because it builds its strings at runtime. So, reverse-engineering it means devirtualizing that interpreter. And the bundle rotates on every request, so even a working solver is code you keep rewriting, not a one-time fix.

Warm up the session

Visit a low-value page before the page you want – a warm-up page on the target's own domain, since an off-domain warm-up earns no history with that deployment. Kasada gives weight to navigation history, and a cold session that goes directly to a high-value endpoint often scores poorly. Our own cold sessions cleared the entry page anyway, so the warm-up is insurance rather than a requirement. Keep it for cold starts that go straight to a deep page. That's where a warm-up should earn its place, and it's what we didn't measure. Warmth is also per-profile, not just per-session, so a brand-new profile can score as cold even on a residential IP that starts positive.

In practice:

  • Reuse a warmed profile when you can. The sample below starts fresh each attempt, which is the safe choice after a block, but throws away any warmth you had.
  • Where you can, read a tab you navigated yourself rather than driving one over the Chrome DevTools Protocol (CDP). The automation channel (Runtime.enable) is a documented signal. It didn't stop the CDP-direct tools tested here, so navigating the tab yourself is a margin worth keeping rather than a rule.

Keep the token fresh 

A live browser regenerates the per-request proof (x-kpsdk-cd) on its own as it issues each request. One clearance still isn't enough, so keep the session alive, and keep its token (x-kpsdk-ct) current. An expired session means the next call returns a 403.

Route through residential or mobile IPs

Layer 2 scores the source address. Residential proxies route through real subscriber IPs, which start with a positive reputation. Mobile IPs help even more. Carrier-grade NAT puts many real subscribers behind each address, so those addresses are costly for any anti-bot to blocklist. For the fundamentals, see what a residential proxy is and how to pick one.

Integration with automation frameworks

Kasada's continuous re-challenge changes how you structure a scraper.

  • Track the token in a session object. Record when the token was issued and resolve before it expires, instead of reacting to a 403 after it happens.
  • Pass the proxy consistently. Route the warm-up page and the target page through the same IP. Switching IPs mid-session undermines the trust Kasada is building. The Playwright web scraping tutorial shows how to attach a proxy across a browser context.
  • Detect a silent re-challenge. A response can return 200 and still be a block. Even a cleared page carries Kasada's script, so that script alone proves nothing. Size is the signal. When the body comes back as a few hundred characters instead of a full page, Kasada has reissued the challenge instead of serving content.
  • Then wait for the real page. There's a third state between the block and the page, and it's the one that will fool you. Read the document too early, and you get Kasada's interstitial, roughly 12 KB of script that restores the referrer while the handshake finishes. The interstitial carries no marker, no title, and no content. So a size check accepts it, and your scraper stores an empty page as a success. Give the handshake several seconds, then confirm the document actually rendered before you trust it.
  • Pace the requests. The 27-page run, described later, used a randomized 2.5 to 5.5 seconds between loads, and nothing degraded across the session. Fixed timing is a standard behavioral signal, so vary it. Pacing starts to matter once you clear the first page and move across pages. Headless, 3 of the 4 Chromium tools never got that far because Kasada re-challenged them on the first page load.

The free DIY stack is 4 parts: a real browser behind a proxy, a warm-up, block detection, and a retry loop. The browser runs the proof-of-work itself. Headful, a stack shaped like this, clears the strict Kasada deployment tested for this guide. Headless, the sample below draws the re-challenge instead, a few hundred characters rather than a page, until the retry loop exhausts and returns None.

# kasada_scraper.py – the minimum viable DIY stack for Kasada.
# A real browser (Nodriver) runs Kasada's JavaScript itself. It runs
# behind a residential proxy, with a warm-up and a re-solve loop.
# headless=False below isn't optional. Run this code headless and
# Kasada returns a few hundred characters instead of a page. Nodriver
# was measured at 744. SeleniumBase UC mode was the one tool that
# survived headless.
# pip install nodriver
import asyncio
import os
import nodriver as uc
# Decodo's usual format is:
# PROXY = "http://YOUR_PROXY_USERNAME:YOUR_PROXY_PASSWORD@gate.decodo.com:7000"
# Nodriver ignores user:pass in --proxy-server, so set DECODO_PROXY to the
# gateway host:port only (gate.decodo.com:7000) and allowlist this machine's
# IP instead (dashboard -> Authentication -> Whitelisted IPs). Leave it unset
# to test the stack without a proxy.
PROXY = os.getenv("DECODO_PROXY")
def looks_blocked(html):
# A cleared page still carries Kasada's script, so finding it proves nothing.
# A re-challenge sends a tiny body, just the bootstrap and no page content.
# Kasada also serves an interstitial while the handshake finishes: about
# 12 KB, no marker, no page. Size alone lets that one through, so also
# check the document actually rendered.
return len(html) < 5000 or "<title" not in html.lower()
async def scrape(target, warmup, attempts=3):
for attempt in range(1, attempts + 1):
args = [f"--proxy-server=http://{PROXY}"] if PROXY else []
browser = await uc.start(browser_args=args, headless=False)
try:
if PROXY:
# A proxy that failed to authenticate looks exactly like a
# fingerprint problem: nothing loads and the block check
# fires. Rule it out here rather than blaming Kasada.
page = await browser.get("https://ip.decodo.com/json")
await asyncio.sleep(3)
if '"ip"' not in await page.get_content():
raise RuntimeError("proxy did not answer -- check auth, not Kasada")
await browser.get(warmup)
await asyncio.sleep(3)
page = await browser.get(target) # browser runs the PoW itself
await asyncio.sleep(12) # 3s returns Kasada's interstitial
html = await page.get_content()
if not looks_blocked(html):
return html
print(f"attempt {attempt}: blocked, retrying with a fresh session")
finally:
browser.stop()
return None # DIY ceiling reached
if __name__ == "__main__":
# Warm-up is on the target's own domain, per the same-domain rule above.
html = asyncio.run(scrape("https://books.toscrape.com/catalogue/page-1.html",
warmup="https://books.toscrape.com/"))
print("got page" if html else "blocked after retries")

Run it against a neutral page first to confirm the stack, then point it at your own authorized target.

Kasada handled, data delivered

Decodo's Web Scraping API bypasses Kasada's proof-of-work challenges, TLS fingerprinting, and behavioral analysis automatically. Your code makes one request and gets clean HTML back.

Open-source tools for bypass

These tools map to different layers. Pick by the layer your target enforces, not by popularity. All of them drive a browser under automation, headless or headful, so pair each one with residential proxies. Across the Chromium tools we tested, the tool choice was invisible in headful mode and decisive in headless mode.

  • Nodriver is the default starting point. It drives Chrome directly without the WebDriver layer that plain Selenium adds. That removes a whole class of automation signals. Headful, the sample above cleared the strict deployment test for this guide and returned the full page. Headless, it drew a re-challenge at 744 characters. See the guide on Nodriver for setup.
  • pydoll works the same way, driving Chrome over CDP with no WebDriver layer. It cleared the same deployment headful, behind a residential proxy, and kept clearing it for 27 pages. Headless, the same tool drew a re-challenge at layer 4.
  • Camoufox and Patchright are the tools you use when layer 4 is the problem. Camoufox is a patched Firefox that spoofs canvas, WebGL, and navigator values at the binary level. Patchright does the same for Playwright. The usual argument for the Firefox path is that most anti-bot tuning targets Chromium. Camoufox drew a re-challenge headful and headless, on the same machine and the same IP where 4 Chromium tools cleared the page. Patchright, which patches Chromium rather than Firefox, cleared the page headful and drew the same re-challenge headless as pydoll and Nodriver.
  • Zendriver is a fork of Nodriver, released 6 times in the year to July 2026, so it's the one to reach for if you want active releases. It wasn't tested against the target in this guide.
  • SeleniumBase UC mode was the only tool tested here that cleared the target headless. That makes it the one to reach for if you can't give a worker a display. It's also a reasonable choice for teams already standardized on Selenium.

Plain Playwright and Selenium without stealth patches leak the automation signals that every stealth tool here exists to remove. Plain Playwright still reports navigator.webdriver as true, and Selenium injects cdc_ variables through ChromeDriver, and detection scripts scan for those variables. Both also drive the browser over CDP, a channel with its own detection history, though it didn't stop the CDP-direct tools tested here.

undetected-chromedriver was used to cover that automation-signal gap, but its last release, version 3.5.5, came in February 2024, more than 2 years before we tested, so the newer tools have replaced it.

Tool

Best for

Kasada ceiling

pydoll

CDP-direct Chrome automation

Cleared headful. Re-challenged headless on the first request

Nodriver

Default Chrome automation without WebDriver signals

Cleared headful. Re-challenged headless

Camoufox

Firefox path when Chromium fingerprints get flagged

Re-challenged on this target, headful and headless

Patchright

Patched Playwright for fingerprint leaks

Cleared headful. Re-challenged headless

SeleniumBase UC mode

Teams already on Selenium

Cleared headful and headless. The only tool tested that survived headless

TLS-impersonation clients

Matching the layer-1 handshake

Can't run the JavaScript proof-of-work

An open-source tool cleared the target on the setup tested here. Every detail of that setup is yours to maintain for as long as you run it.

What does the ceiling look like

We tested these tools on a live high-security deployment in July 2026. Kasada releases changes continuously and rebuilds its bundle on every request, so every number below is from that date. Start with pydoll, which clears the target headful, then run it again headless. The challenge still runs, and the handshake still succeeds. The page still doesn't arrive:

# The first request draws the challenge, and the script clears its handshake
GET https:///429
GET /149e9513-/2d206a39-/ips.js?… → 200 OK
POST /149e9513-/2d206a39-/tl → 200 OK
# The same page, read a moment later
> document.title
''
> document.documentElement.outerHTML.length
727

The block lands on the first page load, before the session has scrolled, clicked, or navigated anywhere. Nothing behavioral has happened yet, so layer 5 can't be what rejected the session. Kasada rejected the environment itself, at layer 4.

That capture is one run. To rule out the network, pin a sticky residential session so the exit IP can't move, then run pydoll twice through it. The exit address is masked below, but it's the same address in both rows. Change nothing but the headless flag:

Run

Exit IP

Body returned

Headful

24.21.x.x

625,543 characters, the full page

Headless

24.21.x.x (same session)

725 characters, the re-challenge

The same browser, still headful, cleared with its GPU disabled and software rendering on, so a missing GPU alone isn't what Kasada is reading. The re-challenge body also drifts a little between runs, so the exact byte count isn't a reliable marker. For this tool, the headless flag decides whether the page arrives.

Repeat that A/B across 5 tools, on one test machine's own IP with no proxy, and the answer isn't the same for every tool. Each tool ran 3 passes in each mode, and we cycled through the 5 rather than finishing one before starting the next, so a change on Kasada's side would land on all of them. Every cell gave the same result all 3 times. Dropping the proxy is why pydoll's headful number below differs from the proxied run above:

Tool

Engine

Headful

Headless

pydoll

Chromium

cleared, 630,302

re-challenged, 729

Nodriver

Chromium

cleared, 820,201

re-challenged, 744

Patchright

Chromium

cleared, 834,956

re-challenged, 744

SeleniumBase UC mode

Chromium

cleared, 643,374

cleared, 800,752

Camoufox

Firefox

re-challenged, 744

re-challenged, 744

With headful, 4 Chromium tools clear the target and return the same page, so on your own machine, they're interchangeable. Headless, 3 of those 4 return a few hundred characters, and one keeps working.

Headful also hides which tool you picked. Headless shows it. A local test tells you the target is reachable, but it doesn't tell you whether the tool you chose will survive the move to a server.

Headful holds up across a session. pydoll walked 27 category and product pages on one sticky residential IP, pacing itself a few seconds between loads. Every page returned real content and real prices, and nothing degraded along the way. That's one session on one target, not a week of scraping at volume, so it's a floor rather than a guarantee.

A server with no monitor can still run a browser headful under a virtual display such as Xvfb. What we measured was the headless flag, not the display, so Xvfb is the next thing to try rather than a known answer. You'd be running and maintaining a display server next to every worker, against challenge code that's rebuilt on every request.

Chrome's newer headless mode is closer to headful than the old one, and it was still the mode in which 3 of the 4 Chromium tools above drew a re-challenge. Before you debug anything else, verify your exit IP with a request to ip.decodo.com/json, because a proxy that quietly failed looks exactly like a fingerprint problem.

Commercial bypass solutions

Building your own Kasada bypass is worth it in one narrow case: you hit a small number of targets, at very high volume, all the time, and you're willing to re-qualify your tool choice as the field moves. A managed service charges per request, so at that volume, the bill scales with the traffic while the engineering time to keep your own stack current doesn't scale with it.

For occasional or exploratory scraping, the opposite is true. Kasada rotates its challenge code on every request, so a hand-tuned scraper needs constant attention to keep up. Kasada is also well funded to sustain that pace, after a $20M raise in early 2026. In Proxyway's 2025 report, one participant half-joked that 2 days of unblocking work used to buy 2 weeks of access, and that the ratio has since inverted.

A managed scraping API solves the proof-of-work, re-solves before the token expires, and runs a fingerprint-matched browser. Your code sends a URL and gets back HTML or structured data. For Kasada, that's Decodo's Web Scraping API.

Mapped to the 5 layers, the Decodo stack divides into 2 parts. IP reputation is about where the request originates. Residential and mobile proxies start the request with positive trust, which every browser approach here still needs, whether DIY or managed. The TLS and browser fingerprints, the proof-of-work, and behavior are about how the client acts. On that side, the Web Scraping API runs a fingerprint-matched browser to clear the challenge server-side and returns parsed data.

This is the build-versus-buy decision in code. Try the DIY path, and if it comes back blocked, hand the URL to a managed endpoint.

# kasada_fallback.py
# pip install requests
import os
import requests
def looks_blocked(status, body):
# A cleared page still carries Kasada's script, so finding it proves nothing.
# A re-challenge sends a tiny body, just the bootstrap and no page content.
# Kasada's interstitial is bigger, around 12 KB, and still has no page in
# it, so confirm the document rendered instead of trusting size alone.
return (status in (403, 429) or len(body) < 5000
or "<title" not in body.lower())
def fetch(url, diy_fetch, managed_fetch, diy_budget=2):
# 1) Try the DIY path first (stealth browser + proxy + proof-of-work).
for attempt in range(diy_budget):
status, body = diy_fetch(url)
if not looks_blocked(status, body):
return {"path": "diy", "attempt": attempt + 1,
"status": status, "body": body}
# 2) DIY degraded past its budget, so escalate to the managed path.
status, body = managed_fetch(url)
return {"path": "managed", "status": status, "body": body}
def decodo_web_scraping_api(url):
# The escalation target. Needs DECODO_API_TOKEN (Base64, from the
# dashboard).
resp = requests.post(
"https://scraper-api.decodo.com/v2/scrape",
# Leave "target" out for an arbitrary site; it selects a site-specific
# template (amazon, google, ...). "headless" here is Decodo's own
# parameter for JavaScript rendering, unrelated to the browser mode
# above: "html" renders the page and returns HTML.
json={"url": url, "headless": "html", "proxy_pool": "premium"},
headers={
"accept": "application/json",
"content-type": "application/json",
"authorization": f"Basic {os.getenv('DECODO_API_TOKEN')}",
},
timeout=120,
)
if resp.status_code != 200:
# Usually the token: DECODO_API_TOKEN unset sends "Basic None".
raise RuntimeError(f"scraping API returned {resp.status_code}: {resp.text[:120]}")
results = resp.json().get("results")
if not results:
# The API answered but could not fetch the target. Its own message
# says which remedy to try, so surface it rather than swallow it.
raise RuntimeError(f"scraping API could not fetch: {resp.text[:160]}")
# With headless="html", content comes back as plain HTML, not base64.
return results[0]["status_code"], results[0]["content"]
if __name__ == "__main__":
# Stubs exercise the control flow: DIY stays blocked, so it escalates.
calls = {"diy": 0}
def fake_diy(url):
calls["diy"] += 1
return 429, "<html>window.KPSDK re-challenge</html>"
def fake_managed(url):
return 200, "<html>" + "x" * 6000 + "</html>"
result = fetch("https://example.com/", fake_diy, fake_managed)
print(f"path={result['path']} status={result['status']} "
f"body_len={len(result['body'])} diy_attempts={calls['diy']}")

Expected output:

path=managed status=200 body_len=6013 diy_attempts=2

Replace diy_fetch with your browser routine, and give decodo_web_scraping_api your token. The full parameter set and target templates are in the docs.

Tool churn is the clearest case for buying. A stealth browser does clear this deployment on your own machine. Run it headless, the way most servers do, and the field narrows sharply, because 3 of the 4 that cleared headful drew a re-challenge headless. One tool survived. You're not choosing a tool once; you're choosing whichever tool still clears this month, against challenge code that's rebuilt on every request.

We tested the Web Scraping API on the same live Kasada target, on the same day in July 2026. That target answered, with a few hundred characters, 4 of the 5 browsers we ran headless. The API returned the full rendered page, 833,967 characters. That's one target on one day, a floor rather than a guarantee, like every number above.

A managed API moves the tool churn to the vendor. The testing stays with you. "Supports Kasada" on a feature list isn't the same as clearing it in production, and the gap between the claim and the result is a question about your target, not about the feature list. Test a managed API against your own page before you commit.

The public build-versus-buy numbers in this market come from vendors, with no independent audit behind them. So run the arithmetic on your own target count, your own volume, and your own engineering time.

Final thoughts

Kasada is among the hardest mainstream anti-bot systems to automate against, and the reason is structural. Of its five layers, three are static (TLS fingerprint, IP reputation, browser fingerprint), and a residential IP with a stealth browser clears the first two. The proof-of-work is active, but a real browser clears it just by loading the page. The real separator is the browser fingerprint: in testing, four headful stacks cleared, but only SeleniumBase UC mode also cleared headless. The recipe that lasts is familiar: run a real or patched browser, route through residential IPs, warm up the session, and re-solve continuously. When that becomes maintenance you'd rather not own, a managed endpoint handles the re-qualification for you.

Stop reverse-engineering Kasada

Kasada's detection evolves weekly. Decodo stays ahead of Kasada's anti-bot stack so you don't spend every Monday patching your workaround.

Share article:

About the author

Justinas Tamasevicius

Director of Engineering

Justinas Tamaševičius is Director of Engineering with over two decades of expertise in software development. What started as a self-taught passion during his school years has evolved into a distinguished career spanning backend engineering, system architecture, and infrastructure development.

Connect with Justinas via LinkedIn.

All information on Decodo Blog is provided on an as is basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Decodo Blog or any third-party websites that may belinked therein.

Frequently asked questions

How do I know if a site uses Kasada?

The dependable signal is the x-kpsdk- response headers, part of Kasada's protocol. A script that loads from a path starting with /149e9513-… backs it up, and a block returns a plain 403 or 429 with no CAPTCHA. The session cookie name varies because the customer chooses it (KP_UIDz and tkrm_alpekz are 2 examples).

Can you bypass Kasada without a browser?

Not reliably. A TLS-impersonating HTTP client can match the handshake that layer 1 inspects, but it can't run the JavaScript proof-of-work challenge. The proof-of-work token comes from a real or patched browser executing the page, so a browserless approach fails at layer 3.

Why does my scraper fail after a few minutes?

Kasada's token isn't a one-time clearance. A live browser regenerates the per-request proof on its own, so the session token (x-kpsdk-ct) is the part that lapses. When it expires or when the scraper stops resolving, the next request returns a 403 or 429.

Why does my scraper work locally but fail on my server?

Moving to a server changes 2 things, not one. Check the address first, because it's the cheaper fix: your laptop goes out through a residential ISP range, which starts positive at layer 2, while a server goes out through a datacenter range, which starts negative. Verify your exit IP at ip.decodo.com/json. Check the display next, because a server usually runs the browser headless, and 3 of the 4 Chromium browsers we tested drew a re-challenge on the first request. That failure looks silent, since the challenge script still loads and its handshake still returns 200. Check the body size, because a re-challenge comes back a few hundred characters, whereas a real page comes back hundreds of thousands. Then check your tool, because the fourth browser cleared headless, and the 4 Chromium browsers are indistinguishable on a laptop.

Which tool is best for bypassing Kasada?

On the target we tested in July 2026, SeleniumBase UC mode. It was the only one of 5 open-source tools that cleared both headful and headless, and headless is how most servers run a browser. pydoll, Nodriver, and Patchright all cleared headful and drew a re-challenge headless. The full numbers for all 5 are in the comparison above. This is one target on one day rather than a standing ranking, because Kasada rebuilds its challenge code on every request and the answer moves with it.

Are residential proxies enough to bypass Kasada?

Not on their own. Residential and mobile proxies clear the IP-reputation layer and start with positive trust, which every browser approach here still needs. Proxies don't address the proof-of-work or fingerprint layers, though. You still need a real browser to execute the challenge. Of the 4 Chromium browsers we ran headless, 3 executed it and were still re-challenged at the fingerprint layer, so the browser you pick matters as much as the proxy.

What makes Kasada harder than Cloudflare?

The proof-of-work layer. Cloudflare, Akamai, and HUMAN rely more on passive fingerprinting than Kasada does, and sometimes on a visible challenge. Kasada adds an active computational challenge in rotating, obfuscated JavaScript, with no CAPTCHA to solve, so passive evasion alone isn't enough to pass.

Is it legal to scrape a site that uses Kasada?

That depends on the data, not on Kasada. An anti-bot system is a technical control, not a legal one, so clearing it doesn't settle the question either way. Kasada appears on financial and ticketing platforms, where scraping behind a login or touching transactional flows carries much higher legal risk than collecting public, non-authenticated data. Check the target's terms of service and the laws in your jurisdiction first, and get legal advice before you run at volume, because public data still raises terms-of-service, copyright, and database-right questions.

How to Bypass Cloudflare

How to Bypass Cloudflare: Complete Guide to Anti-Bot Evasion

Cloudflare is a massive global cloud network that sits firmly between your scraper and the data you need, blocking all requests that fail its multi-layered detection system. It powers nearly 21% of all websites globally, meaning that 1-in-5 sites rely on this network. Therefore, knowing how to bypass it is essential for serious scrapers. This practical walkthrough covers detection methods, tools like Puppeteer and Playwright, and both DIY approaches and managed solutions, including proxy strategies and web scraping APIs.

How to Bypass PerimeterX: Detection Methods, Tools, and Practical Workarounds

PerimeterX, now HUMAN, is a cybersecurity platform that employs multiple detection techniques to accurately identify and block threats to web applications. Since numerous high-traffic websites rely on PerimeterX, it's almost inevitable that developers will encounter it when web scraping. This guide explains how PerimeterX detects bots, how to bypass it (tools and strategies), and how to troubleshoot common failures.

Magnifying glass highlighting a robot face icon over a browser window on a dark background

Navigating Anti-Bot Systems: Pro Tips For 2026

With the rapid improvements in artificial intelligence technologies, it seems that 2026 will present some new challenges for web scraping enthusiasts and professionals. Over the years, anti-bot systems have become increasingly sophisticated, which makes extracting valuable data from websites a true challenge. As businesses intensify their efforts to protect against automated bots, traditional web scraping methods are being put to the test. The surge in anti-bot measures is not only due to heightened cybersecurity awareness but also signifies a shift in the digital ecosystem and growing competition. As a result, those who want to leverage publicly available data need to recalibrate their strategies to navigate and circumvent anti-bot systems.

If CAPTCHAs and IP bans were not on your bingo card for 2026, our comprehensive guide is a must-read. We’ve sat down with our scraping gurus and discussed the best practices, gathered all the pro tips, and summarized what’s coming next for anti-bot systems and scrapers. As 2026 approaches, it demands a proactive approach to understanding, outsmarting, and ultimately thriving in the face of escalating anti-bot measures, so grab a cup of coffee and dive into our guide.

If you can't access the whole article, make sure you have disabled your ad blocker

© 2018-2026 decodo.com (formerly smartproxy.com). All Rights Reserved