SurSolver

BlogDataDome Solver API: Get the datadome Cookie

DataDome Solver API: Get the datadome Cookie

· Updated · 9 min read

By the end of this guide you will be able to tell a DataDome block from other anti-bot walls, take the challenge URL out of the response, call createTask with DatadomeSliderTask, and replay the returned cookie.

What DataDome gives you back

DataDome scores each request with IP reputation, TLS and HTTP/2 fingerprints, browser fingerprinting, behaviour and per-customer models. Most visitors never see it; a suspicious one gets a device-check interstitial or a slider.

A solve does not produce a form token. It produces a cookie: a validated datadome value bound to the exit IP and the fingerprint it was earned with. That is why the proxy and the user agent matter more here than the challenge itself. A shorter overview is on the DataDome solver page.

With SurSolver the result has three keys. cookies is an array of { name, value, domain?, path? } objects and is what you replay. token and userAgent appear only when the solver reports them. Some other solver APIs return a full Set-Cookie string, a bare value or a map per domain, so code written for them will not read this response unchanged: loop over cookies.

Fingerprint cheat-sheet

These markers identify DataDome. Look for them in the blocked response and in the DevTools Network and Application panels.

Marker Where you see it What it tells you
datadome cookie on the response Set-Cookie header, Application → Cookies DataDome is in front of the site
HTTP 403 with a small HTML body containing var dd Page request A block on a page
JSON body { "url": "https://geo.captcha-delivery.com/..." } XHR or API endpoint The same block, for an API call; url is the challenge URL
Challenge URL path /captcha/ JSON url, iframe src Slider
Challenge URL path /interstitial/ JSON url, iframe src Device check, no puzzle
rt:'c' and c.js inside var dd Block page source Slider
rt:'i' and i.js inside var dd Block page source Interstitial
t=fe or t=bv in the challenge URL Query string fe: solvable. bv: your IP is banned
tags.js, often https://js.datadome.co/tags.js or a custom domain Script tag in the page Telemetry script; it runs before any block
window.ddjskey, window.ddoptions DevTools console Per-site configuration for that telemetry script

A same-origin GET of the blocked document on leboncoin.fr returning 403, with the response body's `var dd` object printed in the console

The var dd object carries the fields cid, hsh, t, s, e, rt and, on interstitials, b. A commonly used way to read it is to take the text after var dd=, swap single quotes for double quotes and parse it as JSON. The function below does that and also reads the url from a JSON block:

solve.py
import json
import re
from html import unescape
from urllib.parse import parse_qs, urlparse

CHALLENGE_HOST = 'captcha-delivery.com'
DD_OBJECT = re.compile(r'var\s+dd\s*=\s*(\{.*?\})', re.DOTALL)
IFRAME_SRC = re.compile(
    r'''src=["'](https://[^"']*captcha-delivery\.com/(?:captcha|interstitial)/[^"']*)["']'''
)


def find_challenge_url(body):
    try:
        data = json.loads(body)
    except ValueError:
        data = None
    if isinstance(data, dict) and isinstance(data.get('url'), str):
        url = data['url']
        return url if CHALLENGE_HOST in urlparse(url).netloc else None
    match = IFRAME_SRC.search(body)
    return unescape(match.group(1)) if match else None


def parse_dd(body):
    match = DD_OBJECT.search(body)
    if not match:
        return None
    try:
        return json.loads(match.group(1).replace("'", '"'))
    except ValueError:
        return None


def read_datadome_block(body):
    """Describe a DataDome block, or return None when the body is not one."""
    challenge_url = find_challenge_url(body)
    dd = parse_dd(body)
    if challenge_url is None and dd is None:
        return None
    if challenge_url:
        kind = 'interstitial' if '/interstitial/' in challenge_url else 'slider'
        t_value = parse_qs(urlparse(challenge_url).query).get('t', [None])[0]
    else:
        kind = {'c': 'slider', 'i': 'interstitial'}.get(dd.get('rt'), 'unknown')
        t_value = dd.get('t')
    return {'kind': kind, 'challenge_url': challenge_url, 'ip_banned': t_value == 'bv'}

Which layer is blocking you

A solve only helps at one layer of the stack. The table maps what you observe to the layer and to the action that fits.

What you observe Layer Does a solve help? What to do
t=bv in the challenge URL IP reputation No Change the proxy IP, trigger the block again and extract fresh values
/captcha/ URL with t=fe Slider challenge Yes Send DatadomeSliderTask
/interstitial/ URL Device check Not documented See the note below the table
Cookie returned, replay still gets 403 Client TLS, headers No Paste a cookie copied from a real browser into your client; if that also gets 403, fix the client
Blocked on the first request from a datacenter range IP type No Use a sticky residential or mobile proxy
Your own automation drags the slider and fails Behaviour No The slider measures mouse, touch and timing, not only the end position; send the task to the API instead

SurSolver documents one DataDome task type, DatadomeSliderTask. The documentation does not describe interstitial URLs or a separate tags task, so if your target serves /interstitial/, ask support before you build on it. If an interstitial escalates, the next challenge URL has a /captcha/ path and is a slider.

Collect the parameters

DatadomeSliderTask takes two required values, the proxy, and two optional ones.

Field Required What to send
websiteURL Yes The full URL of the blocked page
captchaUrl Yes The full captcha-delivery.com challenge URL with its session parameters
proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword Yes A sticky proxy; there is no proxyless form. The proxy guide lists the string and split formats
userAgent No The user agent of the session that got blocked
datadomeCookie No The current, blocked datadome cookie value

Load the protected page through the proxy and with the user agent you will replay with, and check the t value first. Take captchaUrl verbatim from the JSON url or from the challenge iframe src. If the 403 page only contains var dd, other solver providers document building the URL from its fields: cid becomes initialCid, hsh becomes hash, the current datadome cookie becomes cid, and t, s and e carry over. The full page URL goes in as referer, percent-encoded. Compare your result with the iframe src of a real browser before you send it.

The captcha-delivery.com challenge request URLs (with `t=fe`) printed from the console on leboncoin.fr

Read the blocked datadome cookie from Application → Cookies or from the Set-Cookie header of the 403. Extract every value right before createTask; they are dynamic per render. The general parameter-hunting guide covers the DevTools workflow.

This script triggers the block, solves it and replays the cookie. It uses read_datadome_block from above. Replace the placeholders with your own values.

solve.py
import time
from urllib.parse import urlparse

import requests

API = 'https://api.sursolver.com'
CLIENT_KEY = 'YOUR_API_KEY'
PAGE_URL = 'https://www.example.com/products'
USER_AGENT = 'YOUR_BROWSER_USER_AGENT'
PROXY = {
    'proxyType': 'http',
    'proxyAddress': 'proxy.example.com',
    'proxyPort': 8000,
    'proxyLogin': 'YOUR_PROXY_LOGIN',
    'proxyPassword': 'YOUR_PROXY_PASSWORD',
}
POLL_SECONDS = 3
POLL_ATTEMPTS = 60
REQUEST_TIMEOUT = 30


def call(path, body):
    # API errors are HTTP 200 with errorId 1; anything else is a transport failure.
    response = requests.post(f'{API}{path}', json=body, timeout=REQUEST_TIMEOUT)
    response.raise_for_status()
    data = response.json()
    if data['errorId'] != 0:
        raise RuntimeError(f"{data['errorCode']}: {data['errorDescription']}")
    return data


def solve_datadome(challenge_url):
    task = {
        'type': 'DatadomeSliderTask',
        'websiteURL': PAGE_URL,
        'captchaUrl': challenge_url,
        'userAgent': USER_AGENT,
        **PROXY,
    }
    created = call('/createTask', {'clientKey': CLIENT_KEY, 'task': task})
    for _ in range(POLL_ATTEMPTS):
        result = call('/getTaskResult', {'clientKey': CLIENT_KEY, 'taskId': created['taskId']})
        if result['status'] == 'ready':
            return result['solution']
        time.sleep(POLL_SECONDS)
    raise TimeoutError(f"task {created['taskId']} was not ready in {POLL_ATTEMPTS} polls")


proxy_url = (
    f"{PROXY['proxyType']}://{PROXY['proxyLogin']}:{PROXY['proxyPassword']}"
    f"@{PROXY['proxyAddress']}:{PROXY['proxyPort']}"
)
session = requests.Session()
session.proxies.update({'http': proxy_url, 'https': proxy_url})
session.headers['User-Agent'] = USER_AGENT

first = session.get(PAGE_URL, timeout=REQUEST_TIMEOUT)
if first.status_code != 403:
    print('not blocked:', first.status_code)
    raise SystemExit(0)

block = read_datadome_block(first.text)
if block is None:
    raise SystemExit('the 403 is not a DataDome block')
if block['ip_banned']:
    raise SystemExit('t=bv: change the proxy IP and trigger the block again')
if block['challenge_url'] is None:
    raise SystemExit('no challenge URL in the block; build it from var dd first')

solution = solve_datadome(block['challenge_url'])
host = urlparse(PAGE_URL).hostname
for cookie in solution['cookies']:
    session.cookies.set(
        cookie['name'],
        cookie['value'],
        domain=cookie.get('domain') or host,
        path=cookie.get('path') or '/',
    )
if solution.get('userAgent'):
    session.headers['User-Agent'] = solution['userAgent']

second = session.get(PAGE_URL, timeout=REQUEST_TIMEOUT)
print('replay status:', second.status_code)

userAgent in the solution is conditional, so the script keeps your own user agent when it is absent. requests also has its own TLS and header fingerprint: if the replay returns 403 with a fresh cookie, test the same cookie in a real browser before you suspect the solve.

Replay rules

  • Use the same sticky proxy and the same user agent for the block, the solve and the replay. A rotating proxy breaks the chain, and datacenter addresses are frequently blocked. See proxies for captcha solving.
  • Header order, TLS fingerprint and, for regional sites, Accept-Language should match what a real browser sends for that user agent. See browser headers and user agents.
  • Reuse the cookie instead of solving on every request. One solver provider's documentation puts its effective lifetime at about an hour per origin and device fingerprint; no second source states a lifetime, so treat that as a rough guide. Solve again when the site blocks you.
  • Each accepted task is charged and a task that finishes as failed automatically returns its original charge once, so reusing a working cookie also saves money (billing).

Common failures

Symptom Likely cause Fix
ERROR_FAILED while polling The solve failed; the error description gives the reason The task was charged and is final. Check the proxy, user agent and challenge URL, then submit a new task
ERROR_INVALID_REQUEST A required field is missing or malformed, or the proxy is in an unreadable format The description names the field. Nothing was charged
ERROR_NO_SLOT_AVAILABLE Rate limit or queue admission Wait for the (retry in Ns) hint and retry with jitter
HTTP 500 without errorId Unexpected fault Check /getBalance before resubmitting, since a retried createTask is a new charged task

Next steps

Create an API key in the dashboard, trigger a block from your own sticky proxy, and send the challenge URL with DatadomeSliderTask in one createTask call. The accepted parameters are listed by the public catalog (GET /v1/task-types), and polling and failure handling are in the task lifecycle guide. If your target uses PerimeterX instead, the matching guide is PerimeterX bypass.

More articles