DataDome

DataDome

 

Create a task through sync api https://sync.ez-captcha.com/createSyncTask, and get the result directly

If you obtain an invalid token, please contact us. It will usually work normally after we optimize it.

Task Type

Task Type

Description

Price(USD)

Task Type

Description

Price(USD)

DataDomeTaskProxyless

DataDome solution

step1: $0.1/k

step2: $2.5/k

Support Options

Type

Support Status

Type

Support Status

Slider

✔ support

Interstitial

✔ support

Parameter Structure

Parameter

Type

Required

Description

Parameter

Type

Required

Description

clientKey

string

true

Your client key

type

string

true

DataDomeTaskProxyless

step

string

true

1 = Get the challenge URL; 2 = Get Release Results

referer

string

true

In Step 1, enter the page URL; in Step 2, enter the URL returned by Step 1

html_b64

string

true

Base64-encoded HTML text

user_agent

string

false

Step 1 is optional, Step 2 is required.

Currently, only the versions listed below are supported:

  1. Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36

Important Notes:

  1. Currently, the only supported languages are: en-US;q=0.9

Usage Instructions

The EZ DataDome processing workflow consists of two steps:


Step 1:Submit the initial page (Get the challenge)

Interface

POST https://sync.ez-captcha.com/createSyncTask

Request Parameters

{ "clientKey": "your_client_key", "task": { "type": "DataDomeTaskProxyless", "step": "1", # 1 = Get the challenge URL "html_b64": "<HTML text in Base64>", "referer": "https://target.com/page" } }

Parameter Description

Parameter

Description

Parameter

Description

step=1

Get the DataDome challenge URL

html_b64

Page HTML (must be Base64-encoded)

referer

URL of the current page


Return results

{ "errorId": 0, "solution": {url: "https://geo.captcha-delivery.com/captcha/xxx"} }

Step 2:Submit Challenge Page (Get Solution)


Request Parameters

{ "clientKey": "your_client_key", "task": { "type": "DataDomeTaskProxyless", "step": "2", "html_b64": "<Base64 Challenge Page HTML>", "referer": "https://geo.captcha-delivery.com/captcha/xxx", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36" } }

Return Results

{ "errorId": 0, "solution": { "kind": "slider", // Specify whether the trigger is a Slider or an Interstitial "url": "https://geo.captcha-delivery.com/validate/xxx", "body": null } }

Step 3:Submit verification results

Execute different requests based on the returned `kind`


1. slider type

GET {solution.url}

2. interstitial type

POST {solution.url} Content-Type: application/x-www-form-urlencoded {solution.body}

Return Results

{ "errorId": 0, "view": "redirect", "cookie": "datadome=xxxxx" }

Instructions for Using Cookies

After the API request is processed successfully, it will return:

datadome=xxxxxxxxxxxx

Directions for use:

  • Write to the current session

  • Used for authenticating subsequent requests

Demo

# -*- coding: utf-8 -*- """ DataDome solver service - slider integration example Requirements: pip install requests Usage: 1) Configure API_URL, CLIENT_KEY, and USER_AGENT below. 2) Configure a residential proxy in PROXY. Slider challenges require a clean residential IP, and the same outbound IP must be used for the entire flow. POST {API_URL} JSON body: { "clientKey": "<your API key>", "task": { "type": "DataDomeTaskProxyless", "step": "1" or "2", # 1 = get challenge URL; 2 = get pass result "html_b64": "<base64-encoded HTML>", "referer": "<page URL for step1; step1 URL for step2>", "user_agent": "<fixed user agent specified by EZ>" } } A successful JSON response has errorId=0 and the result in solution. A failed response has errorId!=0; see errorCode and errorDescription. The step1 solution is {"url": challenge URL}. For a slider, the step2 solution is {"url": verification URL, "body": null}. Always use the locally configured USER_AGENT. Important requirements: - user_agent must be the fixed value provided by EZ. Do not modify or rotate it. - Encode all HTML as base64 before assigning it to html_b64. - Use one session, preserve site cookies, and keep the same outbound IP for the entire flow. """ import base64 import json import re import time import random import requests def generate_random_number(min_length=8, max_length=13): length = random.randint(min_length, max_length) first_digit = random.randint(1, 9) remaining_digits = [random.randint(0, 9) for _ in range(length - 1)] number = int(''.join(map(str, [first_digit] + remaining_digits))) return number class Banned(Exception): """The current outbound IP or session is banned by DataDome.""" # ============================ Configuration ============================ # Solver service endpoint. API_URL = "https://sync.ez-captcha.com/createSyncTask" # Your API key. CLIENT_KEY = "11b6106e35e0476890a95291a543657658" # Fixed user agent. USER_AGENT = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36" ) # Residential proxy required by slider sites. Example: "http://user:pass@host:port" PROXY = "" + str( generate_random_number(8)) + "" # A page on the target site that triggers DataDome. TARGET_URL = "https://www.xxx.com/xxx/xxx" # Delay before verification to simulate human solving time. SOLVE_DELAY = 3 # ====================================================================== def _b64(text: str) -> str: """Encode HTML text as base64.""" return base64.b64encode(text.encode("utf-8")).decode("ascii") def _is_blocked(html: str) -> bool: """Return whether the HTML is a DataDome hard-block page.""" return "hardblock" in html def _sec_ch_ua(ua: str) -> str: """Build a sec-ch-ua value whose version matches the fixed user agent.""" m = re.search(r"Chrome/(\d+)", ua) v = m.group(1) if m else "148" return f'"Chromium";v="{v}", "Google Chrome";v="{v}", "Not/A)Brand";v="99"' def call_solver(step: int, html: str, referer: str) -> object: """ Call the solver service. step: 1 gets the challenge URL; 2 gets the pass result. html: Raw HTML for the current page or challenge page. This function encodes it. referer: Page URL for step1; the URL returned by step1 for step2. Returns: step1 -> dict: {"url": challenge URL} step2 -> dict: {"url", "body", "kind"}; slider uses GET, and interstitial uses POST. """ payload = { "clientKey": CLIENT_KEY, "task": { "type": "DataDomeTaskProxyless", "step": str(step), "html_b64": _b64(html), "referer": referer, "user_agent": USER_AGENT, }, } resp = requests.post(API_URL, json=payload, timeout=60) data = resp.json() print(f"step{step} solver response: {data}") resp.raise_for_status() # errorId != 0 indicates failure; details are in errorCode/errorDescription. if data.get("errorId", 0) != 0: code = data.get("errorCode", "") desc = data.get("errorDescription", "") # "blocked" means DataDome banned the IP/session. Retry with a new IP. if "blocked" in f"{code} {desc}".lower(): raise Banned(desc or code or "blocked") raise RuntimeError(f"Solver failed: {code} {desc}".strip()) solution = data.get("solution") # Some gateways return solution as a JSON string; normalize it here. if isinstance(solution, str): try: solution = json.loads(solution) except json.JSONDecodeError: pass return solution def _nav_headers(fetch_site: str) -> dict: """Build navigation headers. Use none initially and same-origin to verify.""" return { "User-Agent": USER_AGENT, "Accept": ("text/html,application/xhtml+xml,application/xml;q=0.9," "image/avif,image/webp,image/apng,*/*;q=0.8"), "Accept-Language": "en-US;q=0.9", "Accept-Encoding": "gzip, deflate, br", "sec-ch-ua": _sec_ch_ua(USER_AGENT), "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Site": fetch_site, "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", } def _challenge_headers(referer: str) -> dict: """Build headers for loading the cross-site slider iframe.""" return { "User-Agent": USER_AGENT, "Accept": ("text/html,application/xhtml+xml,application/xml;q=0.9," "image/avif,image/webp,image/apng,*/*;q=0.8"), "Accept-Language": "en-US;q=0.9", "Accept-Encoding": "gzip, deflate, br", "sec-ch-ua": _sec_ch_ua(USER_AGENT), "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "Sec-Fetch-Site": "cross-site", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Dest": "iframe", "Referer": referer, } def _check_headers(referer: str, ua: str) -> dict: """Build same-origin CORS headers for fetching /captcha/check.""" return { "User-Agent": ua, "Accept": "*/*", "Accept-Language": "en-US;q=0.9", "Accept-Encoding": "gzip, deflate, br", "sec-ch-ua": _sec_ch_ua(ua), "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": referer, } def _post_headers(referer: str, ua: str) -> dict: """Build headers for posting an interstitial payload to DataDome.""" return { "User-Agent": ua, "Accept": "*/*", "Accept-Language": "en-US;q=0.9", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "https://geo.captcha-delivery.com", "Referer": referer, "sec-ch-ua": _sec_ch_ua(ua), "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"Windows"', "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", } def _submit(session, sol: dict, challenge_url: str, used_ua: str): """Submit the solution to DataDome and return its cookie string. A slider uses GET; an interstitial posts the form body. If kind is absent, an empty body is treated as a slider according to the service contract. Return None when verification does not pass. """ kind = (sol.get("kind") or "").strip().lower() body = sol.get("body") submit_url = sol["url"] is_slider = (kind == "slider") if kind else (not body) if is_slider: res = session.get( submit_url, headers=_check_headers(challenge_url, used_ua), timeout=30 ).json() else: res = session.post( submit_url, headers=_post_headers(challenge_url, used_ua), data=(body or "").encode("utf-8"), timeout=30, ).json() print(f"Verification response: {res}") # An interstitial passes with view=redirect. Both flows return a cookie. if not is_slider and res.get("view") != "redirect": print(f"Not passed: view={res.get('view')}. Retry with a new proxy/IP.") return None cookie_field = res.get("cookie", "") if not cookie_field: print(f"Not passed: no cookie returned: {res}. Retry with a new proxy/IP.") return None return cookie_field def main() -> None: session = requests.Session() if PROXY: session.proxies = {"http": PROXY, "https": PROXY} try: # 1) Fetch the target page. Expect HTTP 403 with a challenge script. print("[1/5] Fetching target page ...") html = session.get(TARGET_URL, headers=_nav_headers("none"), timeout=30).text if _is_blocked(html): raise Banned("The initial page is a hard-block page") if "var dd" not in html: print("DataDome was not triggered; the request may already be allowed.") return # 2) Step 1: get the slider challenge URL. print("[2/5] Getting challenge URL ...") step1_solution = call_solver(1, html, TARGET_URL) challenge_url = ( step1_solution.get("url") if isinstance(step1_solution, dict) else step1_solution ) if not isinstance(challenge_url, str) or not challenge_url: raise RuntimeError(f"Unexpected step1 response: {step1_solution!r}") # 3) Fetch the slider challenge page. print("[3/5] Fetching challenge page ...") challenge_html = session.get( challenge_url, headers=_challenge_headers(TARGET_URL), timeout=30 ).text if _is_blocked(challenge_html): raise Banned("The challenge page is a hard-block page") # 4) Step 2: solve and get the submission URL/body. print("[4/5] Solving challenge ...") sol = call_solver(2, challenge_html, challenge_url) used_ua = USER_AGENT # Keep the fixed local UA; ignore server-side UA values. # Wait before submission to simulate human solving time. time.sleep(SOLVE_DELAY) # 5) Submit by kind (slider GET or interstitial POST) and get the cookie. print("[5/5] Submitting solution ...") cookie_field = _submit(session, sol, challenge_url, used_ua) if not cookie_field: return print("Passed; received the datadome cookie") # 6) Verify access with the new datadome cookie. # Remove the old challenge cookie from the initial 403 response first; # otherwise it can conflict with the new cookie and cause another 403. # Keep all other site cookies stored by Session. new_dd = cookie_field.split(";")[0].split("=", 1)[-1] for c in list(session.cookies): if c.name == "datadome": session.cookies.clear(c.domain, c.path, c.name) session.cookies.set("datadome", new_dd, domain=".xxx.com", path="/") v = session.get(TARGET_URL, headers=_nav_headers("same-origin"), timeout=30) ok = v.status_code in (200, 302) and "var dd" not in v.text and not _is_blocked(v.text) status = "allowed" if ok else "still blocked" print(f"Target verification: HTTP {v.status_code}, {status}") except Banned as e: # Retry the full flow with a clean residential proxy/IP after a ban. print(f"Banned: {e}. Retry with a different residential proxy/IP.") if __name__ == "__main__": main()