Kasada
Solve Kasada-protected origins. Requires the p.js path and FP/TL hosts; returns the x-kpsdk-* headers to replay on the target.
Kasada
Kasada uses fingerprinting and a proof-of-work challenge to gate the protected origin. The solver replays the full handshake and returns the x-kpsdk-* headers that grant access.
Request
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | Yes | kasada (or alias kasada-bypass). |
url | string | Yes | The Kasada-protected origin. |
user_agent | string | No | Recommended — use the same UA you'll replay the headers with. |
ua_version | int | No | Major Chrome version, 80–300. Should match user_agent. |
kasada_config | object | Yes | The site-specific paths and hosts (see below). |
proxy | string | No | Optional. |
kasada_config
| Field | Required | Notes |
|---|---|---|
p_js_path | Yes | Path to Kasada's p.js script, starting with /. No ... Max 512 chars. |
fp_host | Yes | Hostname serving the fingerprint page. |
tl_host | Yes | Hostname for the /tl token endpoint. |
cd_constant | No | Hex string used as the CD proof-of-work constant when the site requires one. |
Response
{
"success": true,
"type": "kasada",
"headers": {
"x-kpsdk-ct": "...",
"x-kpsdk-cd": "{...JSON-encoded proof...}",
"x-kpsdk-v": "j-1.2.xxx",
"x-kpsdk-h": "..."
},
"cost": 0.0015
}Forward the x-kpsdk-* headers verbatim on your next request to the protected origin. Always pair them with the same User-Agent you submitted in the solve call — Kasada fingerprints both.
How it works (under the hood)
- The solver fetches
p.jsto detect the Kasada SDK version. - It loads the fingerprint page on
fp_hostand executesips.js. - It posts the fingerprint payload to
tl_host/tlfor tokens. - It completes the
/mfchandshake and computes the CD proof-of-work. - The headers come back.
You don't need to drive any of this — you just need the config values.
Finding the config values
Most are visible in the browser's Network panel on the protected page:
- Filter for
p.js— note the full path, that'sp_js_path. - The
Host:header on that request isfp_host(usually the origin). - Filter for
/tl— theHost:header on that request istl_host.
fp_host and tl_host are often the same hostname, but not always. When the target is an API subdomain (e.g. gql.twitch.tv), the TL host is usually that subdomain while the FP host is the app shell (e.g. passport.twitch.tv).
Performance
| Metric | Typical value |
|---|---|
| Solve time | 3-10 seconds |
| Success rate | 90%+ |
Solve time is dominated by the proof-of-work step; expect more variance than Turnstile.
Examples
import requests
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
r = requests.post(
"https://api.nslsolver.com/solve",
headers={"X-API-Key": "YOUR_KEY"},
json={
"type": "kasada",
"url": "https://passport.twitch.tv",
"user_agent": ua,
"ua_version": 145,
"kasada_config": {
"p_js_path": "/149e9513-.../2d206a39-.../p.js",
"fp_host": "passport.twitch.tv",
"tl_host": "gql.twitch.tv",
},
},
timeout=180,
)
solved = r.json()
session = requests.Session()
session.headers.update(solved["headers"])
session.headers["User-Agent"] = ua
# session.post("https://gql.twitch.tv/gql", json=...)const ua = "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36";
const r = await fetch("https://api.nslsolver.com/solve", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.NSL_API_KEY,
},
body: JSON.stringify({
type: "kasada",
url: "https://passport.twitch.tv",
user_agent: ua,
ua_version: 145,
kasada_config: {
p_js_path: "/149e9513-.../2d206a39-.../p.js",
fp_host: "passport.twitch.tv",
tl_host: "gql.twitch.tv",
},
}),
signal: AbortSignal.timeout(180_000),
});
const { headers } = await r.json();type KasadaConfig struct {
PJSPath string `json:"p_js_path"`
FPHost string `json:"fp_host"`
TLHost string `json:"tl_host"`
CDConstant string `json:"cd_constant,omitempty"`
}
type KasadaReq struct {
Type string `json:"type"`
URL string `json:"url"`
UserAgent string `json:"user_agent"`
UAVersion int `json:"ua_version"`
KasadaConfig KasadaConfig `json:"kasada_config"`
}
payload, _ := json.Marshal(KasadaReq{
Type: "kasada",
URL: "https://passport.twitch.tv",
UserAgent: "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36",
UAVersion: 145,
KasadaConfig: KasadaConfig{
PJSPath: "/149e9513-.../2d206a39-.../p.js",
FPHost: "passport.twitch.tv",
TLHost: "gql.twitch.tv",
},
})
req, _ := http.NewRequest("POST", "https://api.nslsolver.com/solve", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", os.Getenv("NSL_API_KEY"))
client := &http.Client{Timeout: 180 * time.Second}curl -X POST https://api.nslsolver.com/solve \
-H "Content-Type: application/json" \
-H "X-API-Key: $NSL_API_KEY" \
-d '{
"type": "kasada",
"url": "https://passport.twitch.tv",
"user_agent": "Mozilla/5.0 ... Chrome/145.0.0.0 Safari/537.36",
"ua_version": 145,
"kasada_config": {
"p_js_path": "/149e9513-.../2d206a39-.../p.js",
"fp_host": "passport.twitch.tv",
"tl_host": "gql.twitch.tv"
}
}'Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
kasada_config required for kasada type | Missing the object | Always include the config. |
kasada_config.p_js_path contains invalid characters | Bad path | Must start with /, no .., alphanumeric / _-/. only. |
Empty headers returned | Wrong p_js_path (Kasada SDK rev'd) | Re-inspect the current p.js URL. |
ua_version must be between 80 and 300 | Out-of-range version | Use the major Chrome number from your user_agent. |
| Headers rejected by target | UA mismatch | Replay with the same UA you sent to /solve. |