DataDome bypass
Title:
How to bypass DataDome captcha with SolveCaptcha
Description:
Learn how to detect a DataDome challenge, extract a fresh captcha URL, submit it to SolveCaptcha with the correct proxy and User-Agent, retrieve the datadome cookie, and reuse it in the original Python or browser session.
H1:
How to bypass DataDome captcha
Content:
DataDome can interrupt scraping, browser automation, QA, and data collection workflows when a request is classified as automated or suspicious. Unlike a basic image captcha, DataDome verification is tied to the browser session, IP address, User-Agent, challenge URL, and the datadome cookie.
With the SolveCaptcha DataDome solver, you can submit the current challenge URL together with the original proxy and User-Agent. SolveCaptcha processes the challenge and returns the cookie required to continue the session.
The complete workflow is:
Open the protected page
→ detect the DataDome challenge
→ extract the current captcha URL
→ verify the t parameter
→ submit the URL, proxy, and User-Agent
→ poll the SolveCaptcha API
→ retrieve and parse the datadome cookie
→ apply it to the original session
→ repeat the protected request
This guide covers:
- identifying a DataDome block;
- extracting
captcha_urlthrough DevTools, HTML, or a 403 response; - detecting blocked proxy IPs;
- preserving the same proxy and User-Agent;
- creating and polling a SolveCaptcha task;
- applying the returned cookie to Requests or Selenium;
- handling expired challenges, API errors, and browser-fingerprint differences;
- reporting accepted and rejected solutions.
Use this method only on websites you own or are authorized to test, scrape, or automate.
What is DataDome?
DataDome is a bot-management and fraud-prevention platform used to protect websites, mobile applications, and APIs.
A protected website can evaluate signals such as:
- IP reputation;
- request frequency;
- browser fingerprint;
- JavaScript execution;
- cookies;
- request headers;
- TLS characteristics;
- navigation patterns;
- mouse and keyboard activity;
- previous behavior associated with the session.
When a request is classified as suspicious, the website may return an HTTP 403 response or display a captcha through an iframe hosted on a DataDome captcha-delivery domain.
A typical captcha URL starts with:
https://geo.captcha-delivery.com/captcha/
After the challenge is completed, DataDome returns a new cookie:
datadome=COOKIE_VALUE
That cookie must be used with the same browser environment and proxy session.
How DataDome captcha solving works
The SolveCaptcha DataDome method is cookie-based.
The workflow is:
- Send a request to the protected website.
- Detect the DataDome block.
- Extract the captcha URL from the iframe or block response.
- Confirm that the captcha URL contains a valid
tparameter. - Submit the captcha URL, page URL, proxy, and User-Agent to SolveCaptcha.
- Receive a task ID.
- Poll the task until the result is ready.
- Receive a complete
datadomecookie string. - Add the cookie to the original browser or HTTP session.
- Retry the protected request using the same proxy and User-Agent.
SolveCaptcha API endpoints
Create a task through:
POST https://api.solvecaptcha.com/in.php
Retrieve the result through:
GET https://api.solvecaptcha.com/res.php
The required method is:
method=datadome
Do not use unsupported task structures or parameter names such as:
DataDomeSliderTask
createTask
getTaskResult
clientKey
websiteURL
captchaUrl
proxyAddress
proxyPort
The SolveCaptcha DataDome method uses:
key
method
captcha_url
pageurl
userAgent
proxy
proxytype
Required DataDome parameters
The API request requires the following values:
| Parameter | Required | Description |
|---|---|---|
key |
Yes | Your SolveCaptcha API key |
method |
Yes | Must be datadome |
captcha_url |
Yes | Full URL from the captcha iframe src attribute |
pageurl |
Yes | Full URL where the captcha appeared |
userAgent |
Yes | User-Agent used to access the target website |
proxy |
Yes | Proxy in login:password@host:port format |
proxytype |
Yes | HTTP, HTTPS, SOCKS4, or SOCKS5 |
json |
No | Set to 1 to receive JSON responses |
A proxy is mandatory for SolveCaptcha DataDome tasks.
The proxy and User-Agent should match those used by the browser or HTTP client that received the challenge.
Why the same proxy is required
A DataDome captcha solution is associated with the IP address used to load and solve the challenge.
The following configuration is incorrect:
Target request: Proxy A
SolveCaptcha task: Proxy B
Retry request: Proxy C
Use:
Target request: Proxy A
SolveCaptcha task: Proxy A
Retry request: Proxy A
Avoid rotating the proxy between:
- the initial blocked request;
- captcha URL extraction;
- SolveCaptcha task creation;
- result retrieval;
- cookie application;
- the retried target request.
If the proxy rotates automatically, use a sticky session.
Why the User-Agent must match
The API requires the exact User-Agent used to access the protected page.
Example:
Mozilla/5.0 (...) Chrome/... Safari/537.36
Use the same value in:
- the original website request;
- the SolveCaptcha
userAgentparameter; - the retry after applying the cookie.
Do not send one User-Agent to DataDome and another to SolveCaptcha.
Use a complete User-Agent from a current browser rather than an abbreviated or invented value.
How to identify and extract a DataDome challenge
Open browser DevTools and inspect the failed request.
Common indicators include:
captcha-delivery.com
geo.captcha-delivery.com
interstitial.captcha-delivery.com
datadome
initialCid
cid
hash
t=fe
A challenge may appear:
- as a visible iframe;
- as an interstitial page;
- after an HTTP 403 response;
- after a protected button is clicked;
- during login or registration;
- after several repeated requests.
A normal HTTP 403 response does not automatically prove that DataDome generated the block. Confirm that the response or loaded resources contain DataDome-related URLs.
Method 1: Find captcha_url in the Elements panel
Open the Elements panel and search for:
captcha-delivery.com
You may find an iframe similar to:
<iframe
src="https://geo.captcha-delivery.com/captcha/?initialCid=...&hash=...&t=fe&referer=...">
</iframe>
Copy the complete value of the src attribute.
That complete URL becomes:
captcha_url
Do not copy only the domain or path.
The query parameters are part of the current challenge and must be preserved.
Method 2: Find captcha_url in the Network panel
The Network panel is usually the most reliable method.
- Open DevTools.
- Select Network.
- Enable Preserve log.
- Clear the existing requests.
- Trigger the protected action.
- Search for
captcha-delivery. - Select the challenge request.
- Copy its complete request URL.
Check both document and iframe requests.
The URL may appear after the original target request returns HTTP 403.
Method 3: Find the iframe URL in the browser console
Run:
const captchaFrame = [
...document.querySelectorAll(
"iframe[src]"
)
].find((iframe) =>
/captcha-delivery\.com\/captcha/i.test(
iframe.src
)
);
if (captchaFrame) {
console.log(
captchaFrame.src
);
} else {
console.log(
"DataDome captcha iframe was not found."
);
}
This script reads only the iframe URL. It does not attempt to access the cross-origin iframe contents.
If the iframe is not present in the top-level document, use the Network panel.
Extract the iframe URL from HTML
The following function finds a DataDome captcha iframe in an HTML response:
import html
import re
from urllib.parse import urljoin
DATADOME_IFRAME_PATTERN = re.compile(
r"""
<iframe
[^>]+
src=["']
(
[^"']*
captcha-delivery\.com/captcha/
[^"']*
)
["']
""",
flags=re.IGNORECASE | re.VERBOSE,
)
def extract_captcha_iframe_url(
page_html: str,
page_url: str,
) -> str | None:
match = DATADOME_IFRAME_PATTERN.search(
page_html
)
if not match:
return None
raw_url = html.unescape(
match.group(1)
)
return urljoin(
page_url,
raw_url,
)
Example:
captcha_url = extract_captcha_iframe_url(
response.text,
response.url,
)
if captcha_url:
print(captcha_url)
Extract DataDome block parameters from a 403 response
Some websites return an inline JavaScript object instead of a fully rendered iframe.
It may resemble:
var dd = {
cid: "INITIAL_CID",
hsh: "HASH_VALUE",
t: "fe",
s: "SITE_ID",
e: "ENCRYPTED_VALUE"
};
The response can also set a temporary datadome cookie through the Set-Cookie header.
In that case, the captcha URL can be constructed from:
dd.cid;dd.hsh;dd.t;dd.s;dd.e;- the temporary
datadomecookie; - the protected page URL.
Use this only as a fallback when the actual iframe URL is unavailable.
import re
from urllib.parse import urlencode
class DataDomeExtractionError(RuntimeError):
"""Raised when DataDome parameters cannot be extracted."""
def extract_js_property(
source: str,
property_name: str,
) -> str | None:
pattern = re.compile(
rf"""
["']?
{re.escape(property_name)}
["']?
\s*:\s*
["']
([^"']+)
["']
""",
flags=re.IGNORECASE | re.VERBOSE,
)
match = pattern.search(source)
if not match:
return None
return match.group(1)
def extract_datadome_configuration(
page_html: str,
) -> dict[str, str]:
object_match = re.search(
r"""
(?:var\s+)?
dd
\s*=\s*
\{
(.*?)
\}
\s*;?
""",
page_html,
flags=re.IGNORECASE
| re.DOTALL
| re.VERBOSE,
)
if not object_match:
raise DataDomeExtractionError(
"DataDome configuration object was not found"
)
source = object_match.group(1)
values = {
name: extract_js_property(
source,
name,
)
for name in (
"cid",
"hsh",
"t",
"s",
"e",
)
}
missing = [
name
for name, value in values.items()
if not value
]
if missing:
raise DataDomeExtractionError(
"Missing DataDome values: "
+ ", ".join(missing)
)
return {
name: str(value)
for name, value in values.items()
}
def build_datadome_captcha_url(
configuration: dict[str, str],
temporary_cookie: str,
page_url: str,
) -> str:
if configuration["t"] == "bv":
raise RuntimeError(
"The current proxy IP is blocked by DataDome"
)
if configuration["t"] != "fe":
raise RuntimeError(
"Unsupported DataDome challenge type: "
f"{configuration['t']}"
)
query = urlencode(
{
"initialCid": configuration["cid"],
"hash": configuration["hsh"],
"cid": temporary_cookie,
"t": configuration["t"],
"referer": page_url,
"s": configuration["s"],
"e": configuration["e"],
}
)
return (
"https://geo.captcha-delivery.com/"
f"captcha/?{query}"
)
Use the temporary cookie from the HTTP response:
temporary_cookie = response.cookies.get(
"datadome"
)
if not temporary_cookie:
raise DataDomeExtractionError(
"Temporary datadome cookie was not found"
)
configuration = (
extract_datadome_configuration(
response.text
)
)
captcha_url = build_datadome_captcha_url(
configuration=configuration,
temporary_cookie=temporary_cookie,
page_url=response.url,
)
Check the t parameter before submitting the task
Before sending the captcha URL to SolveCaptcha, inspect its t query parameter.
A valid challenge normally contains:
t=fe
Example:
https://geo.captcha-delivery.com/captcha/?initialCid=...&t=fe&referer=...
If the URL contains:
t=bv
the proxy IP has been blocked by DataDome.
Do not submit that challenge repeatedly. Replace the proxy, open the protected page again, and obtain a new captcha URL.
Python validation:
from urllib.parse import parse_qs, urlparse
def validate_captcha_url(captcha_url: str) -> None:
query = parse_qs(
urlparse(captcha_url).query
)
challenge_type = query.get(
"t",
[None],
)[0]
if challenge_type == "bv":
raise RuntimeError(
"The proxy IP is blocked by DataDome. "
"Change the proxy and request a new challenge."
)
if (
challenge_type is not None
and challenge_type != "fe"
):
raise RuntimeError(
"Unsupported DataDome challenge type: "
f"{challenge_type}"
)
If the t parameter is absent, do not invent it. Submit the original iframe URL only when it is a genuine DataDome captcha URL.
Challenge freshness
The complete captcha URL belongs to the current challenge.
It may become invalid when:
- the page is reloaded;
- the proxy changes;
- the browser session changes;
- the challenge is completed;
- DataDome generates another challenge;
- the URL is stored and reused later;
- the target page changes.
Extract captcha_url immediately before creating the SolveCaptcha task.
Do not cache it for future sessions.
Do not combine:
- a URL from one proxy;
- a User-Agent from another browser;
- a page URL from another route;
- cookies from another session.
Get your SolveCaptcha API key
Copy your API key from the SolveCaptcha account settings.
Store it in an environment variable.
Linux or macOS:
export SOLVECAPTCHA_API_KEY="YOUR_API_KEY"
Windows PowerShell:
$env:SOLVECAPTCHA_API_KEY="YOUR_API_KEY"
Do not publish the key in source code, screenshots, logs, or public repositories.
Create a SolveCaptcha DataDome task
Example request:
curl --request POST \
--url https://api.solvecaptcha.com/in.php \
--data-urlencode "key=YOUR_API_KEY" \
--data-urlencode "method=datadome" \
--data-urlencode "captcha_url=https://geo.captcha-delivery.com/captcha/?initialCid=...&t=fe&referer=..." \
--data-urlencode "pageurl=https://example.com/protected-page" \
--data-urlencode "userAgent=YOUR_BROWSER_USER_AGENT" \
--data-urlencode "proxy=login:[email protected]:8000" \
--data-urlencode "proxytype=http" \
--data-urlencode "json=1"
A successful response contains the task ID:
{
"status": 1,
"request": "2033679236"
}
Save the value from request.
Retrieve the DataDome cookie
Request the result through:
GET https://api.solvecaptcha.com/res.php
Parameters:
key=YOUR_API_KEY
action=get
id=2033679236
json=1
Example:
curl "https://api.solvecaptcha.com/res.php?key=YOUR_API_KEY&action=get&id=2033679236&json=1"
While the task is being processed:
{
"status": 0,
"request": "CAPCHA_NOT_READY"
}
Wait five seconds and repeat the request using the same task ID.
A successful response contains the complete cookie string:
{
"status": 1,
"request": "datadome=H7LmQvR8kP3D...XyJcTt9LsAbQW2N7FZk; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax"
}
The response is not only the cookie value. It can also contain attributes such as:
Domain;Path;Max-Age;Secure;SameSite.
Parse the response before adding it to the session.
Complete Python DataDome solver
Install Requests:
python -m pip install requests
For SOCKS proxies:
python -m pip install "requests[socks]"
Set the required environment variables:
export SOLVECAPTCHA_API_KEY="YOUR_API_KEY"
export TARGET_URL="https://example.com/protected-page"
export TARGET_PROXY="login:[email protected]:8000"
export TARGET_PROXY_TYPE="http"
export BROWSER_USER_AGENT="YOUR_BROWSER_USER_AGENT"
Create datadome_solver.py:
#!/usr/bin/env python3
from __future__ import annotations
import html
import json
import os
import re
import time
from dataclasses import asdict, dataclass
from http.cookies import SimpleCookie
from pathlib import Path
from typing import Any
from urllib.parse import (
parse_qs,
urlencode,
urljoin,
urlparse,
)
import requests
CREATE_TASK_URL = "https://api.solvecaptcha.com/in.php"
GET_RESULT_URL = "https://api.solvecaptcha.com/res.php"
POLL_INTERVAL_SECONDS = 5
SOLVE_TIMEOUT_SECONDS = 180
COOKIE_FILE = Path("datadome-cookie.json")
DATADOME_IFRAME_PATTERN = re.compile(
r"""
<iframe
[^>]+
src=["']
(
[^"']*
captcha-delivery\.com/captcha/
[^"']*
)
["']
""",
flags=re.IGNORECASE | re.VERBOSE,
)
class DataDomeError(RuntimeError):
"""Base error for the DataDome integration."""
class DataDomeExtractionError(DataDomeError):
"""Raised when challenge parameters cannot be extracted."""
class DataDomeProxyBlockedError(DataDomeError):
"""Raised when the challenge reports t=bv."""
class SolveCaptchaError(DataDomeError):
"""Raised when SolveCaptcha returns an API error."""
@dataclass(frozen=True)
class DataDomeCookie:
value: str
domain: str
path: str
secure: bool
raw: str
def parse_api_response(
response: requests.Response,
) -> dict[str, Any]:
response.raise_for_status()
try:
payload = response.json()
except requests.JSONDecodeError as exc:
raise SolveCaptchaError(
"SolveCaptcha returned invalid JSON: "
f"{response.text[:500]}"
) from exc
if not isinstance(payload, dict):
raise SolveCaptchaError(
f"Unexpected API response: {payload!r}"
)
return payload
def proxy_for_requests(
proxy: str,
proxy_type: str,
) -> str:
normalized_type = proxy_type.lower()
if normalized_type == "socks5":
scheme = "socks5h"
elif normalized_type == "socks4":
scheme = "socks4a"
elif normalized_type in {
"http",
"https",
}:
scheme = normalized_type
else:
raise ValueError(
"Proxy type must be HTTP, HTTPS, "
"SOCKS4, or SOCKS5"
)
return f"{scheme}://{proxy}"
def validate_captcha_url(
captcha_url: str,
) -> None:
parsed = urlparse(captcha_url)
if (
"captcha-delivery.com"
not in parsed.hostname
if parsed.hostname
else True
):
raise DataDomeExtractionError(
"The URL is not a DataDome "
"captcha-delivery URL"
)
query = parse_qs(parsed.query)
challenge_type = query.get(
"t",
[None],
)[0]
if challenge_type == "bv":
raise DataDomeProxyBlockedError(
"DataDome reports t=bv. "
"The proxy IP is blocked and must be replaced."
)
if (
challenge_type is not None
and challenge_type != "fe"
):
raise DataDomeExtractionError(
"Unsupported DataDome challenge type: "
f"{challenge_type}"
)
def extract_iframe_url(
page_html: str,
page_url: str,
) -> str | None:
match = DATADOME_IFRAME_PATTERN.search(
page_html
)
if not match:
return None
captcha_url = urljoin(
page_url,
html.unescape(
match.group(1)
),
)
validate_captcha_url(
captcha_url
)
return captcha_url
def extract_js_property(
source: str,
property_name: str,
) -> str | None:
pattern = re.compile(
rf"""
["']?
{re.escape(property_name)}
["']?
\s*:\s*
["']
([^"']+)
["']
""",
flags=re.IGNORECASE | re.VERBOSE,
)
match = pattern.search(source)
if not match:
return None
return match.group(1)
def extract_dd_configuration(
page_html: str,
) -> dict[str, str]:
object_match = re.search(
r"""
(?:var\s+)?
dd
\s*=\s*
\{
(.*?)
\}
\s*;?
""",
page_html,
flags=re.IGNORECASE
| re.DOTALL
| re.VERBOSE,
)
if not object_match:
raise DataDomeExtractionError(
"DataDome configuration object "
"was not found"
)
source = object_match.group(1)
result = {
name: extract_js_property(
source,
name,
)
for name in (
"cid",
"hsh",
"t",
"s",
"e",
)
}
missing = [
name
for name, value in result.items()
if not value
]
if missing:
raise DataDomeExtractionError(
"Missing DataDome configuration: "
+ ", ".join(missing)
)
return {
name: str(value)
for name, value in result.items()
}
def build_captcha_url(
configuration: dict[str, str],
temporary_cookie: str,
page_url: str,
) -> str:
challenge_type = configuration["t"]
if challenge_type == "bv":
raise DataDomeProxyBlockedError(
"The current proxy IP is blocked "
"by DataDome"
)
if challenge_type != "fe":
raise DataDomeExtractionError(
"Unsupported challenge type: "
f"{challenge_type}"
)
query = urlencode(
{
"initialCid": configuration["cid"],
"hash": configuration["hsh"],
"cid": temporary_cookie,
"t": challenge_type,
"referer": page_url,
"s": configuration["s"],
"e": configuration["e"],
}
)
captcha_url = (
"https://geo.captcha-delivery.com/"
f"captcha/?{query}"
)
validate_captcha_url(
captcha_url
)
return captcha_url
def find_captcha_url(
response: requests.Response,
) -> str:
iframe_url = extract_iframe_url(
page_html=response.text,
page_url=response.url,
)
if iframe_url:
return iframe_url
temporary_cookie = response.cookies.get(
"datadome"
)
if not temporary_cookie:
raise DataDomeExtractionError(
"The response contains neither "
"a captcha iframe nor a temporary "
"datadome cookie"
)
configuration = extract_dd_configuration(
response.text
)
return build_captcha_url(
configuration=configuration,
temporary_cookie=temporary_cookie,
page_url=response.url,
)
def parse_datadome_cookie(
raw_cookie: str,
page_url: str,
) -> DataDomeCookie:
cookie = SimpleCookie()
try:
cookie.load(raw_cookie)
except Exception as exc:
raise SolveCaptchaError(
"Unable to parse the returned cookie"
) from exc
morsel = cookie.get("datadome")
if morsel is None:
fallback = re.search(
r"(?:^|;\s*)datadome=([^;]+)",
raw_cookie,
flags=re.IGNORECASE,
)
if not fallback:
raise SolveCaptchaError(
"SolveCaptcha response does not "
"contain a datadome cookie"
)
value = fallback.group(1)
domain = (
urlparse(page_url).hostname
or ""
)
path = "/"
secure = page_url.startswith(
"https://"
)
else:
value = morsel.value
domain = (
morsel["domain"]
or urlparse(page_url).hostname
or ""
)
path = morsel["path"] or "/"
secure = bool(
morsel["secure"]
)
if not value:
raise SolveCaptchaError(
"The returned datadome cookie is empty"
)
return DataDomeCookie(
value=value,
domain=domain,
path=path,
secure=secure,
raw=raw_cookie,
)
class SolveCaptchaDataDomeClient:
def __init__(
self,
api_key: str,
request_timeout: float = 30,
) -> None:
if not api_key:
raise ValueError(
"SolveCaptcha API key is required"
)
self.api_key = api_key
self.request_timeout = request_timeout
self.session = requests.Session()
def create_task(
self,
*,
captcha_url: str,
page_url: str,
user_agent: str,
proxy: str,
proxy_type: str,
) -> str:
validate_captcha_url(
captcha_url
)
payload = {
"key": self.api_key,
"method": "datadome",
"captcha_url": captcha_url,
"pageurl": page_url,
"userAgent": user_agent,
"proxy": proxy,
"proxytype": proxy_type,
"json": 1,
}
response = self.session.post(
CREATE_TASK_URL,
data=payload,
timeout=self.request_timeout,
)
result = parse_api_response(
response
)
if result.get("status") != 1:
error_code = str(
result.get(
"request",
"UNKNOWN_ERROR",
)
)
raise SolveCaptchaError(
"Unable to create DataDome task: "
f"{error_code}"
)
task_id = str(
result.get("request", "")
).strip()
if not task_id:
raise SolveCaptchaError(
"SolveCaptcha did not return "
"a task ID"
)
return task_id
def get_result(
self,
task_id: str,
page_url: str,
) -> DataDomeCookie | None:
response = self.session.get(
GET_RESULT_URL,
params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1,
},
timeout=self.request_timeout,
)
result = parse_api_response(
response
)
if result.get("status") == 1:
raw_cookie = str(
result.get("request", "")
).strip()
return parse_datadome_cookie(
raw_cookie=raw_cookie,
page_url=page_url,
)
error_code = str(
result.get(
"request",
"UNKNOWN_ERROR",
)
)
if error_code == "CAPCHA_NOT_READY":
return None
raise SolveCaptchaError(
f"DataDome task {task_id} failed: "
f"{error_code}"
)
def solve(
self,
*,
captcha_url: str,
page_url: str,
user_agent: str,
proxy: str,
proxy_type: str,
max_wait: float = SOLVE_TIMEOUT_SECONDS,
) -> tuple[str, DataDomeCookie]:
task_id = self.create_task(
captcha_url=captcha_url,
page_url=page_url,
user_agent=user_agent,
proxy=proxy,
proxy_type=proxy_type,
)
deadline = time.monotonic() + max_wait
while time.monotonic() < deadline:
time.sleep(
POLL_INTERVAL_SECONDS
)
cookie = self.get_result(
task_id=task_id,
page_url=page_url,
)
if cookie is not None:
return task_id, cookie
raise TimeoutError(
f"SolveCaptcha task {task_id} "
f"was not completed within "
f"{max_wait:.0f} seconds"
)
def report(
self,
task_id: str,
accepted: bool,
) -> None:
response = self.session.get(
GET_RESULT_URL,
params={
"key": self.api_key,
"action": (
"reportgood"
if accepted
else "reportbad"
),
"id": task_id,
"json": 1,
},
timeout=self.request_timeout,
)
parse_api_response(
response
)
def save_cookie(
cookie: DataDomeCookie,
) -> None:
COOKIE_FILE.write_text(
json.dumps(
asdict(cookie),
indent=2,
),
encoding="utf-8",
)
def load_cookie() -> DataDomeCookie | None:
if not COOKIE_FILE.is_file():
return None
try:
payload = json.loads(
COOKIE_FILE.read_text(
encoding="utf-8"
)
)
return DataDomeCookie(
value=str(payload["value"]),
domain=str(payload["domain"]),
path=str(
payload.get(
"path",
"/",
)
),
secure=bool(
payload.get(
"secure",
True,
)
),
raw=str(
payload.get(
"raw",
"",
)
),
)
except (
KeyError,
TypeError,
ValueError,
json.JSONDecodeError,
):
return None
def apply_cookie(
session: requests.Session,
cookie: DataDomeCookie,
) -> None:
session.cookies.set(
"datadome",
cookie.value,
domain=cookie.domain,
path=cookie.path,
secure=cookie.secure,
)
def main() -> None:
api_key = os.environ.get(
"SOLVECAPTCHA_API_KEY",
"",
).strip()
target_url = os.environ.get(
"TARGET_URL",
"",
).strip()
proxy = os.environ.get(
"TARGET_PROXY",
"",
).strip()
proxy_type = os.environ.get(
"TARGET_PROXY_TYPE",
"http",
).strip().lower()
user_agent = os.environ.get(
"BROWSER_USER_AGENT",
"",
).strip()
if not api_key:
raise SystemExit(
"Set SOLVECAPTCHA_API_KEY"
)
if not target_url:
raise SystemExit(
"Set TARGET_URL"
)
if not proxy:
raise SystemExit(
"Set TARGET_PROXY"
)
if not user_agent:
raise SystemExit(
"Set BROWSER_USER_AGENT"
)
proxy_url = proxy_for_requests(
proxy=proxy,
proxy_type=proxy_type,
)
session = requests.Session()
session.proxies.update(
{
"http": proxy_url,
"https": proxy_url,
}
)
session.headers.update(
{
"User-Agent": user_agent,
"Accept": (
"text/html,application/xhtml+xml,"
"application/xml;q=0.9,"
"image/avif,image/webp,*/*;q=0.8"
),
"Accept-Language": (
"en-US,en;q=0.9"
),
}
)
saved_cookie = load_cookie()
if saved_cookie:
apply_cookie(
session,
saved_cookie,
)
response = session.get(
target_url,
timeout=30,
allow_redirects=True,
)
if response.status_code < 400:
print(
"The page is accessible. "
"No new DataDome solution is required."
)
return
if response.status_code != 403:
raise RuntimeError(
"The target returned an unexpected "
f"HTTP status: {response.status_code}"
)
captcha_url = find_captcha_url(
response
)
print(
"DataDome captcha URL:",
captcha_url,
)
client = SolveCaptchaDataDomeClient(
api_key=api_key
)
task_id, cookie = client.solve(
captcha_url=captcha_url,
page_url=response.url,
user_agent=user_agent,
proxy=proxy,
proxy_type=proxy_type,
)
apply_cookie(
session,
cookie,
)
save_cookie(
cookie
)
retry_response = session.get(
target_url,
timeout=30,
allow_redirects=True,
)
accepted = (
retry_response.status_code < 400
)
if accepted:
client.report(
task_id=task_id,
accepted=True,
)
print(
"DataDome cookie was accepted."
)
print(
"HTTP status:",
retry_response.status_code,
)
return
print(
"The request is still blocked."
)
print(
"HTTP status:",
retry_response.status_code,
)
print(
"Do not report the result as incorrect "
"until proxy, User-Agent, challenge age, "
"and request headers have been checked."
)
if __name__ == "__main__":
main()
Run the script:
python datadome_solver.py
A successful solution is saved to:
datadome-cookie.json
The file contains:
{
"value": "H7LmQvR8kP3D...XyJcTt9LsAbQW2N7FZk",
"domain": ".example.com",
"path": "/",
"secure": true,
"raw": "datadome=H7LmQvR8kP3D...; Domain=.example.com; Path=/; Secure"
}
Minimal Python API example
The following version assumes that you already have the complete captcha URL:
import os
import time
import requests
API_KEY = os.environ["SOLVECAPTCHA_API_KEY"]
CAPTCHA_URL = (
"https://geo.captcha-delivery.com/"
"captcha/?initialCid=...&t=fe&referer=..."
)
PAGE_URL = (
"https://example.com/protected-page"
)
USER_AGENT = os.environ[
"BROWSER_USER_AGENT"
]
PROXY = os.environ[
"TARGET_PROXY"
]
create_response = requests.post(
"https://api.solvecaptcha.com/in.php",
data={
"key": API_KEY,
"method": "datadome",
"captcha_url": CAPTCHA_URL,
"pageurl": PAGE_URL,
"userAgent": USER_AGENT,
"proxy": PROXY,
"proxytype": "http",
"json": 1,
},
timeout=30,
).json()
if create_response.get("status") != 1:
raise RuntimeError(
"Task creation failed: "
f"{create_response.get('request')}"
)
task_id = create_response["request"]
while True:
time.sleep(5)
result = requests.get(
"https://api.solvecaptcha.com/res.php",
params={
"key": API_KEY,
"action": "get",
"id": task_id,
"json": 1,
},
timeout=30,
).json()
if result.get("status") == 1:
datadome_cookie = result["request"]
break
if result.get("request") != "CAPCHA_NOT_READY":
raise RuntimeError(
"Task failed: "
f"{result.get('request')}"
)
print(datadome_cookie)
Apply the cookie to a Requests session
Suppose SolveCaptcha returns:
datadome=COOKIE_VALUE; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax
Parse it with SimpleCookie:
from http.cookies import SimpleCookie
import requests
raw_cookie = (
"datadome=COOKIE_VALUE; "
"Domain=.example.com; "
"Path=/; Secure"
)
parsed = SimpleCookie()
parsed.load(raw_cookie)
morsel = parsed["datadome"]
session = requests.Session()
session.cookies.set(
"datadome",
morsel.value,
domain=(
morsel["domain"]
or ".example.com"
),
path=morsel["path"] or "/",
secure=bool(morsel["secure"]),
)
Retry the website with:
- the same proxy;
- the same User-Agent;
- the same headers;
- the same session cookies.
response = session.get(
"https://example.com/protected-page",
timeout=30,
)
print(response.status_code)
Using the cookie in a raw Cookie header
When you do not use a cookie jar, extract only the first cookie pair:
cookie_header = (
solution.set_cookie
.split(";", 1)[0]
.strip()
)
headers = {
"User-Agent": user_agent,
"Cookie": cookie_header,
}
Example value:
datadome=H7LmQvR8kP3D..XyJcTt9LsAbQW2N7FZk
Do not use the complete Set-Cookie string as a request header.
Apply the DataDome cookie in Selenium
For Selenium, open the target domain before adding the cookie.
from http.cookies import SimpleCookie
from urllib.parse import urlparse
from selenium import webdriver
target_url = (
"https://example.com/protected-page"
)
raw_cookie = (
"datadome=COOKIE_VALUE; "
"Domain=.example.com; "
"Path=/; Secure"
)
parsed = SimpleCookie()
parsed.load(raw_cookie)
morsel = parsed["datadome"]
driver = webdriver.Chrome()
try:
driver.get(target_url)
hostname = urlparse(
target_url
).hostname
driver.add_cookie(
{
"name": "datadome",
"value": morsel.value,
"domain": (
morsel["domain"]
or hostname
),
"path": (
morsel["path"]
or "/"
),
"secure": bool(
morsel["secure"]
),
}
)
driver.get(target_url)
finally:
driver.quit()
The Selenium browser must use the same proxy and User-Agent supplied to SolveCaptcha.
Example Selenium proxy configuration depends on whether the proxy requires authentication. For authenticated proxies, a browser extension, Selenium Wire, or a local forwarding proxy may be required.
Headers must match the client
Copying every Chrome header into a Requests script does not automatically make Requests behave like Chrome.
Only send headers that are appropriate for the HTTP client and target request.
Common headers include:
headers = {
"User-Agent": user_agent,
"Accept": (
"text/html,application/xhtml+xml,"
"application/xml;q=0.9,*/*;q=0.8"
),
"Accept-Language": "en-US,en;q=0.9",
"Referer": page_url,
}
Do not blindly hardcode:
sec-ch-ua
sec-fetch-site
sec-fetch-mode
sec-fetch-dest
Their values depend on the real browser request context.
Inspect the target request through DevTools when reproducing a specific workflow.
The returned cookie may not be enough
A valid datadome cookie does not guarantee that every request will be accepted.
The website may also evaluate:
- the proxy IP;
- TLS fingerprint;
- HTTP version;
- browser JavaScript behavior;
- other session cookies;
- navigation sequence;
- request frequency;
- header consistency;
- application-specific authentication;
- API request signatures.
If a Requests client remains blocked while the same cookie works in a browser, the difference may be caused by the browser fingerprint rather than the captcha result.
For browser-dependent websites, apply the cookie inside Selenium, Playwright, Puppeteer, or a remote browser session.
Detect whether the response is a DataDome block
An HTTP 403 response alone does not prove that DataDome caused the block.
Check for indicators such as:
captcha-delivery.com
datadome
initialCid
var dd
geo.captcha-delivery.com/captcha/
Example:
def is_datadome_response(
response: requests.Response,
) -> bool:
source = response.text.lower()
indicators = (
"captcha-delivery.com",
"initialcid",
"var dd",
"datadome",
)
return any(
indicator in source
for indicator in indicators
)
Use it before creating a SolveCaptcha task:
if (
response.status_code == 403
and is_datadome_response(response)
):
print("DataDome challenge detected")
A 403 response can also be caused by:
- access-control rules;
- authentication failure;
- geographic restrictions;
- IP allowlists;
- rate limits;
- custom application logic.
Cookie expiration
Do not assume that every datadome cookie lasts exactly 15 or 30 minutes.
The lifetime depends on the protected website and the attributes returned with the cookie.
Inspect:
Max-Age
Expires
Even when the cookie has a long formal lifetime, DataDome can challenge the session again because of:
- an IP change;
- a User-Agent change;
- inconsistent browser behavior;
- another security rule;
- excessive request volume;
- a new risk decision.
Refresh the cookie when the website presents a new challenge.
Troubleshooting
ERR_PROXY_CONNECTION_FAILED
This error indicates that SolveCaptcha could not connect through the submitted proxy.
Check:
- proxy hostname;
- port;
- username;
- password;
- proxy type;
- firewall restrictions;
- whether the proxy accepts external connections;
- whether the proxy supports HTTPS targets.
Test the proxy separately:
proxy_url = (
"http://login:password@"
"proxy.example.com:8000"
)
response = requests.get(
"https://api.ipify.org?format=json",
proxies={
"http": proxy_url,
"https": proxy_url,
},
timeout=20,
)
print(response.json())
Do not continuously resubmit the same task with a nonworking proxy.
ERROR_CAPTCHA_UNSOLVABLE
For DataDome, this error may indicate:
- blocked proxy;
- expired captcha URL;
- unsupported challenge state;
- mismatched User-Agent;
- proxy connection failure;
- stale session values;
- a captcha URL containing
t=bv.
Recommended action:
- Check the
tparameter. - Test the proxy.
- Replace the proxy if necessary.
- Request the protected page again.
- Extract a new captcha URL.
- Create a new SolveCaptcha task.
- Limit the total number of retries.
Do not reuse an old captcha URL after changing the proxy.
CAPCHA_NOT_READY
This response means the task is still being processed:
{
"status": 0,
"request": "CAPCHA_NOT_READY"
}
Correct behavior:
- Keep the same task ID.
- Wait five seconds.
- Request the result again.
- Stop after a defined timeout.
Do not create another task for the same captcha while the original task is active.
ERROR_WRONG_USER_KEY
The SolveCaptcha API key is invalid.
Check:
SOLVECAPTCHA_API_KEY
Stop submitting tasks until the value is corrected.
ERROR_ZERO_BALANCE
The SolveCaptcha account does not have enough balance.
Stop task creation until the balance is replenished.
The iframe URL cannot be found
The challenge may be returned:
- in the blocked response body;
- after a redirect;
- inside dynamically generated JavaScript;
- only after a protected action;
- inside another frame.
Enable Preserve log and inspect the complete request sequence.
The cookie works once and then fails
The website may have generated a new risk decision or invalidated the session.
Preserve the browser context and avoid changing:
- IP address;
- User-Agent;
- cookies;
- navigation order.
When a new challenge appears, repeat the complete solving workflow.
Persisting the DataDome cookie
A cookie can be stored between script runs, but it should not be assumed to remain valid indefinitely.
Store at least:
{
"value": "COOKIE_VALUE",
"domain": ".example.com",
"path": "/",
"secure": true
}
Before reusing the cookie:
- Use the same proxy or sticky proxy session.
- Use the same User-Agent.
- Request the target page.
- Detect whether DataDome accepts the cookie.
- Solve a new challenge if the cookie has expired or been invalidated.
Do not assume that the Max-Age attribute guarantees acceptance for that full period.
Reporting accepted and rejected results
When the returned cookie is accepted, report:
action=reportgood
Example:
https://api.solvecaptcha.com/res.php?key=YOUR_API_KEY&action=reportgood&id=TASK_ID
Use:
action=reportbad
only when you have confirmed that the returned captcha solution itself was incorrect.
Do not send reportbad when the failure was caused by:
- a changed proxy;
- a mismatched User-Agent;
- an expired captcha URL;
- incorrect cookie-domain handling;
- missing browser cookies;
- request-header differences;
- another DataDome block after excessive traffic;
- an application-level 403 response;
- a TLS or browser-fingerprint mismatch.
Common implementation mistakes
Using createTask and getTaskResult
Incorrect:
createTask
getTaskResult
DataDomeSliderTask
clientKey
Correct:
in.php
res.php
method=datadome
key
Using captchaUrl instead of captcha_url
Incorrect:
captchaUrl=URL
Correct:
captcha_url=URL
Splitting the proxy into multiple parameters
Incorrect:
proxyAddress
proxyPort
proxyLogin
proxyPassword
Correct:
proxy=login:password@host:port
proxytype=HTTP
Using no proxy
DataDome tasks require a proxy.
Sending only the proxy to Requests
The proxy must also be included in the SolveCaptcha task.
Using different proxy sessions
Use the same IP throughout the complete challenge workflow.
Ignoring t=bv
t=bv means the proxy IP is blocked. Replace the proxy before creating another task.
Replacing t=bv with t=fe manually
Do not modify the query parameter. A blocked proxy remains blocked even if the URL text is changed.
Sending the wrong User-Agent
The value must match the client that received the challenge.
Rebuilding an iframe URL unnecessarily
Use the complete iframe src whenever it is available.
Losing URL encoding
The captcha URL contains encoded values. Send it as a normal form field and allow the HTTP library to encode it.
Extracting the wrong cid
The fallback URL can use both:
initialCidfrom the inline DataDome object;cidfrom the temporarydatadomecookie.
They are not necessarily identical.
Saving only part of the response incorrectly
The API returns a complete cookie string. Parse the datadome value and preserve the relevant attributes.
Sending cookie attributes in the Cookie header
Send only:
datadome=COOKIE_VALUE
Do not send Max-Age, Domain, Path, Secure, or SameSite as request cookies.
Applying the cookie to the wrong domain
Use the Domain attribute returned with the cookie or the hostname of the protected website.
Assuming a fixed cookie lifetime
Use the returned expiration attributes and detect new challenge responses instead of assuming a universal lifetime.
Assuming every 403 is DataDome
Confirm the presence of DataDome indicators before creating a task.
Stability recommendations
Keep one session consistent
Preserve:
Proxy
User-Agent
Cookies
Page URL
Browser profile
until the protected workflow is complete.
Validate before creating a task
Check:
- captcha URL hostname;
tparameter;- proxy connectivity;
- User-Agent value;
- page URL;
- API key.
Use controlled retry limits
Do not retry indefinitely.
A practical policy is:
Task polling timeout: 180 seconds
Polling interval: 5 seconds
Proxy replacement: after confirmed proxy failure
New task: only after obtaining a fresh captcha URL
Log useful diagnostic data
Record:
- task ID;
- creation time;
- result time;
- page URL;
- captcha hostname;
- proxy hostname;
- proxy type;
- User-Agent;
- API error code;
- final HTTP status.
Do not log:
- API keys;
- proxy passwords;
- full authentication cookies;
- sensitive account data.
Do not rotate blindly
Replace the proxy only when:
- the URL contains
t=bv; - proxy connection fails;
- the proxy is confirmed blocked;
- the network session is no longer usable.
Every proxy change requires a new page load and a new captcha URL.
Recommended project structure
Separate the workflow into independent components:
datadome_solver/
├── detector.py
├── extractor.py
├── solvecaptcha.py
├── cookies.py
├── browser.py
├── settings.py
└── main.py
Suggested responsibilities:
detector.py
Determines whether a response contains a
DataDome challenge.
extractor.py
Extracts or builds the captcha URL.
solvecaptcha.py
Creates tasks and polls the API.
cookies.py
Parses, stores, and applies datadome cookies.
browser.py
Manages browser sessions, proxies, and
User-Agent values.
settings.py
Loads API credentials and configuration.
main.py
Coordinates the complete solving workflow.
This structure makes it easier to replace Requests with Selenium, Playwright, Puppeteer, or a remote browser without rewriting the SolveCaptcha client.
Pre-launch checklist
Before deploying the integration, confirm:
- [ ] The page is protected by DataDome.
- [ ] The complete captcha iframe URL was captured.
- [ ] The captcha URL belongs to the current page load.
- [ ] The
tparameter isfewhen present. - [ ] The URL does not contain
t=bv. - [ ] The page URL is complete and correct.
- [ ] The User-Agent matches the original browser.
- [ ] A working proxy is configured.
- [ ] The same proxy is used after solving.
- [ ] The proxy format is
login:password@host:port. - [ ]
method=datadomeis used. - [ ] The task ID is read from
request. - [ ] Polling continues every five seconds.
- [ ] The returned cookie string is parsed correctly.
- [ ] Only the
datadomepair is sent in a raw Cookie header. - [ ] A fresh task is created when a new challenge appears.
Frequently asked questions
Which SolveCaptcha method is used for DataDome?
Use:
method=datadome
Is a proxy required?
Yes. The SolveCaptcha DataDome method requires:
proxy
proxytype
Which proxy types are supported?
Supported values are:
HTTP
HTTPS
SOCKS4
SOCKS5
Where can I find captcha_url?
Use the complete src value of the iframe containing the DataDome captcha.
What does t=fe mean?
t=fe identifies a captcha challenge that can be submitted for solving.
What does t=bv mean?
t=bv indicates that the proxy IP is blocked by DataDome. Change the proxy and obtain a new captcha URL.
What does SolveCaptcha return?
It returns a complete datadome cookie string:
datadome=VALUE; Domain=.example.com; Path=/; Secure
How often should the result be checked?
Wait five seconds between requests while the API returns:
CAPCHA_NOT_READY
Can the cookie be used with another proxy?
No. Use the cookie with the same proxy session used for the challenge.
Can the cookie be used with another User-Agent?
Do not change the User-Agent after solving the challenge.
Can I reuse a captcha URL?
No. Treat the captcha URL as short-lived and session-specific.
Why does the cookie work in Selenium but not Requests?
The website may evaluate browser JavaScript, TLS characteristics, HTTP headers, navigation state, and other signals that Requests does not reproduce.
Why am I still receiving HTTP 403?
Possible reasons include:
- blocked proxy;
- stale captcha URL;
- incorrect cookie domain;
- changed User-Agent;
- changed proxy IP;
- another bot-protection rule;
- unsupported HTTP fingerprint;
- missing session cookies;
- excessive request frequency.
Must the proxy be residential?
The API specification does not require a particular commercial proxy category.
The proxy must be accessible, stable, and not blocked by DataDome.
Can I solve DataDome without a browser?
The challenge URL may be extracted from an HTTP response, but the complete flow still requires an accurate page URL, User-Agent, working proxy, and correct cookie handling.
Which programming languages are supported?
Any language capable of sending HTTP requests can use the API, including:
- Python;
- JavaScript;
- PHP;
- Java;
- C#;
- Go;
- Ruby.
Conclusion
A reliable DataDome bypass workflow must preserve the complete challenge context.
The correct process is:
- send the target request through a stable proxy;
- confirm that the response contains DataDome;
- extract the complete captcha iframe URL;
- verify that the URL does not contain
t=bv; - submit the task with
method=datadome; - provide the same proxy and User-Agent;
- poll the result every five seconds;
- parse the complete returned cookie;
- apply it to the original session;
- retry the target request without changing the proxy;
- create a new task when the challenge expires;
- report accepted or genuinely incorrect answers accurately.
Use the SolveCaptcha DataDome API to integrate DataDome captcha handling into authorized Python scraping, browser automation, and QA projects.