> For the complete documentation index, see [llms.txt](https://docs.disasm.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.disasm.dev/datadome/slider-captcha.md).

# Slider CAPTCHA

This guide covers solving a DataDome CAPTCHA with our API. CAPTCHA blocks appear on higher-security endpoints and demand strict browser emulation. Read the [Overview](/datadome/general-information.md) and [TLS & Fingerprinting](/getting-started/tls-fingerprinting.md) first.

{% hint style="warning" %}
Use a Chrome client whose TLS fingerprint, headers and header order match a real browser. Any deviation can invalidate the resulting `datadome` cookie.
{% endhint %}

## The flow at a glance

1. **Detect** a CAPTCHA block (the page references `c.js`).
2. **Build** the captcha URL from the block's `dd` object.
3. **Fetch** the captcha page. If it has a puzzle, download the background and fragment images.
4. **Solve** - send the page (and the images, when there is a puzzle) to our API.
5. **Submit** the solution URL we return.
6. **Store** the `datadome` cookie and retry.

## Step 1 - Detect a CAPTCHA block

A CAPTCHA block references `https://ct.captcha-delivery.com/c.js` and carries a `dd` object with `rt: 'c'`:

```html
<script>
  var dd = {
    'rt': 'c',
    'cid': 'AHrlqAAAAAMASuDzOQlFQzIALvhm3g==',
    'hsh': 'EC3B9FB6F2A31D3AF16C270E6531D2',
    't': 'fe',
    's': 43337,
    'e': '16fbe80bb40a1dfb31c417e8849d8d22',
    'host': 'geo.captcha-delivery.com'
  };
</script>
```

## Step 2 - Build the captcha link

Parse the `dd` object and build the captcha URL. `cid` is the current `datadome` cookie in your jar; `referer` is the page you were blocked on. Only `initialCid` and `referer` are URL-escaped; the rest are sent raw.

{% tabs %}
{% tab title="Go" %}

```go
import (
	"encoding/json"
	"fmt"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

// DDCaptcha is the inline `var dd = {...}` object on a DataDome captcha block.
type DDCaptcha struct {
	Rt   string `json:"rt"`
	Cid  string `json:"cid"`
	Hash string `json:"hsh"`
	T    string `json:"t"`
	S    int64  `json:"s"`
	E    string `json:"e"`
	Host string `json:"host"`
}

var ddBlockRe = regexp.MustCompile(`var dd\s*=\s*(\{[\s\S]*?\})`)

// parseCaptchaDD extracts and decodes the `var dd = {...}` object. The page
// uses single quotes, so they are swapped for double quotes before parsing.
func parseCaptchaDD(html string) (DDCaptcha, error) {
	var dd DDCaptcha
	m := ddBlockRe.FindStringSubmatch(html)
	if m == nil {
		return dd, fmt.Errorf("dd object not found")
	}
	jsonStr := strings.ReplaceAll(m[1], "'", `"`)
	err := json.Unmarshal([]byte(jsonStr), &dd)
	return dd, err
}

// buildCaptchaLink builds the captcha-delivery URL. Only initialCid and referer
// are URL-escaped; every other value is sent raw.
func buildCaptchaLink(dd DDCaptcha, datadomeCookie, referer string) string {
	params := []string{
		"initialCid=" + url.QueryEscape(dd.Cid),
		"hash=" + dd.Hash,
		"cid=" + datadomeCookie,
		"t=" + dd.T,
		"referer=" + url.QueryEscape(referer),
		"s=" + strconv.FormatInt(dd.S, 10),
		"e=" + dd.E,
		"dm=cd",
		"adobe_mc=captcha",
	}
	return "https://geo.captcha-delivery.com/captcha/?" + strings.Join(params, "&")
}
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
// Parse the inline `var dd = {...}` object from a DataDome captcha block.
function parseCaptchaDD(html) {
  const m = html.match(/var dd\s*=\s*(\{[\s\S]*?\})/);
  if (!m) throw new Error("dd object not found");
  return JSON.parse(m[1].replace(/'/g, '"')); // single -> double quotes
}

// Build the captcha-delivery link. Only initialCid and referer are URL-escaped,
// everything else is passed raw.
function buildCaptchaLink(dd, datadomeCookie, referer) {
  const params = [
    "initialCid=" + encodeURIComponent(dd.cid),
    "hash=" + dd.hsh,
    "cid=" + datadomeCookie,
    "t=" + dd.t,
    "referer=" + encodeURIComponent(referer),
    "s=" + String(dd.s),
    "e=" + dd.e,
    "dm=cd",
    "adobe_mc=captcha",
  ];
  return "https://geo.captcha-delivery.com/captcha/?" + params.join("&");
}
```

{% endtab %}

{% tab title="Python" %}

```python
import re
import json
import urllib.parse


def parse_captcha_dd(html):
    """Extract and decode the inline `var dd = {...}` object from a captcha block."""
    m = re.search(r"var dd\s*=\s*(\{[\s\S]*?\})", html)
    if not m:
        raise ValueError("dd object not found")
    return json.loads(m.group(1).replace("'", '"'))


def build_captcha_link(dd, datadome_cookie, referer):
    """Build the captcha-delivery URL. Only initialCid and referer are escaped."""
    params = [
        ("initialCid", urllib.parse.quote(dd["cid"], safe="")),
        ("hash", dd["hsh"]),
        ("cid", datadome_cookie),
        ("t", dd["t"]),
        ("referer", urllib.parse.quote(referer, safe="")),
        ("s", str(dd["s"])),
        ("e", dd["e"]),
        ("dm", "cd"),
        ("adobe_mc", "captcha"),
    ]
    base = "https://geo.captcha-delivery.com/captcha/?"
    return base + "&".join("{}={}".format(k, v) for k, v in params)
```

{% endtab %}
{% endtabs %}

{% hint style="danger" %}
If `t` is `bv`, your proxy is banned. Rotate it and retry until you get a `t=fe` response, and remove any old `datadome` cookies before retrying.
{% endhint %}

## Step 3 - Fetch the captcha page and images

GET the captcha link and parse the `sliderCaptcha({...})` object from the response.

**Check for a puzzle first.** DataDome sometimes returns `noPuzzle: true` - a device-trust pass with no slider to solve. When there is no puzzle there are no images to fetch or send, so go straight to Step 4. Only when there *is* a puzzle do you derive the images:

* the **background** image is the `captchaChallengePath` URL (it ends in `.jpg`);
* the **fragment** (slider piece) is the same URL with `.jpg` replaced by `.frag.png`.

Download both with a GET and Base64-encode the raw bytes of each.

{% tabs %}
{% tab title="Go" %}

```go
import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
)

var (
	sliderArgRe = regexp.MustCompile(`sliderCaptcha\(([\s\S]*?)\)`)
	bareKeyRe   = regexp.MustCompile(`(?m)^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:`)
)

// parseSliderCaptcha extracts the argument object of sliderCaptcha(...) into a
// generic map. Single quotes are normalised and bare keys quoted before parsing.
func parseSliderCaptcha(pageHTML string) (map[string]interface{}, error) {
	m := sliderArgRe.FindStringSubmatch(pageHTML)
	if m == nil {
		return nil, fmt.Errorf("sliderCaptcha argument not found")
	}
	s := strings.ReplaceAll(m[1], "'", `"`)
	s = bareKeyRe.ReplaceAllString(s, `"$1":`)
	var obj map[string]interface{}
	err := json.Unmarshal([]byte(s), &obj)
	return obj, err
}

// hasPuzzle reports whether the slider object contains a puzzle. When noPuzzle
// is true there is no puzzle and no images should be derived or sent.
func hasPuzzle(sliderObj map[string]interface{}) bool {
	noPuzzle, _ := sliderObj["noPuzzle"].(bool)
	return !noPuzzle
}

// deriveImageURLs returns the background and fragment image URLs. Only call it
// when hasPuzzle is true. The fragment is the background with the trailing
// ".jpg" replaced by ".frag.png".
func deriveImageURLs(sliderObj map[string]interface{}) (background, fragment string) {
	background, _ = sliderObj["captchaChallengePath"].(string)
	fragment = strings.TrimSuffix(background, ".jpg") + ".frag.png"
	return background, fragment
}
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
// Parse the object argument of `sliderCaptcha(...)`.
function parseSliderCaptcha(pageHtml) {
  const m = pageHtml.match(/sliderCaptcha\((.*?)\)/s);
  if (!m) throw new Error("sliderCaptcha call not found");
  let body = m[1].replace(/'/g, '"');                                // single -> double quotes
  body = body.replace(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:/gm, '"$1":'); // quote bare keys
  return JSON.parse(body);
}

// noPuzzle === true means there is no puzzle (skip images); otherwise there is one.
function hasPuzzle(sliderObj) {
  return sliderObj.noPuzzle !== true;
}

// Derive the puzzle image URLs. Call only when hasPuzzle is true.
function deriveImageURLs(sliderObj) {
  const background = sliderObj.captchaChallengePath;          // URL ending in .jpg
  const fragment = background.replace(/\.jpg$/, ".frag.png"); // .jpg -> .frag.png
  return { background, fragment };
}
```

{% endtab %}

{% tab title="Python" %}

```python
import re
import json


def parse_slider_captcha(page_html):
    """Extract the argument object passed to `sliderCaptcha(...)`."""
    m = re.search(r"sliderCaptcha\((.*?)\)", page_html, re.DOTALL)
    if not m:
        raise ValueError("sliderCaptcha call not found")
    body = m.group(1).replace("'", '"')
    body = re.sub(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:", r'"\1":', body, flags=re.MULTILINE)
    return json.loads(body)


def has_puzzle(slider_obj):
    """True unless `noPuzzle` is explicitly true (then there is no puzzle)."""
    return slider_obj.get("noPuzzle") is not True


def derive_image_urls(slider_obj):
    """Return (background, fragment) image URLs. Only call when has_puzzle is True."""
    background = slider_obj["captchaChallengePath"]
    fragment = re.sub(r"\.jpg$", ".frag.png", background)
    return background, fragment
```

{% endtab %}
{% endtabs %}

## Step 4 - Solve with our API

Send the request to the [captcha endpoint](/datadome/api-reference.md). Use `type: "image"` for the slider, and include `background_image` and `fragment_image` **only when there was a puzzle**:

```json
{
  "type": "image",
  "captcha_link": "https://geo.captcha-delivery.com/captcha/?initialCid=...",
  "html": "<base64 of the captcha page>",
  "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
  "background_image": "<base64, only when there is a puzzle>",
  "fragment_image": "<base64, only when there is a puzzle>",
  "ip_address": "<your proxy IP>"
}
```

For the audio challenge instead, send `type: "audio"` with the Base64 audio file in `audio_challenge`. On higher-security sites it can be worth switching between image and audio.

The response includes `captcha_submit_url` - the fully built URL to GET to apply the solution - plus a `headers` map of client hints to send on your requests.

## Step 5 - Submit the solution

GET the `captcha_submit_url` returned by our API. Match real Chrome behaviour, paying particular attention to:

* `user-agent`
* `sec-ch-ua` and related client hints
* `referer` matching the captcha page
* header order

## Step 6 - Store the cookie and retry

A success returns the cookie:

```json
{
  "cookie": "datadome=...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax"
}
```

Parse the `datadome` value out of that `cookie` string, store it for the target domain (replacing any existing one), then retry your original request.

{% tabs %}
{% tab title="Go" %}

```go
import "net/http"

// parseDatadomeCookie extracts the datadome cookie value from a Set-Cookie line.
func parseDatadomeCookie(setCookie string) string {
	header := http.Header{}
	header.Add("Set-Cookie", setCookie)
	resp := http.Response{Header: header}
	for _, c := range resp.Cookies() {
		if c.Name == "datadome" {
			return c.Value
		}
	}
	return ""
}
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
// Extract the `datadome` cookie value from a full Set-Cookie line.
function parseDatadomeCookie(setCookie) {
  for (const part of setCookie.split(";")) {
    const kv = part.trim();
    if (kv.startsWith("datadome=")) return kv.slice("datadome=".length);
  }
  return "";
}
```

{% endtab %}

{% tab title="Python" %}

```python
import http.cookies


def parse_datadome_cookie(set_cookie):
    """Return the `datadome` cookie value from a full Set-Cookie line."""
    jar = http.cookies.SimpleCookie()
    jar.load(set_cookie)
    return jar["datadome"].value
```

{% endtab %}
{% endtabs %}

If it is still blocked, retry up to three times. If it keeps failing, rotate your proxy, clear any old cookies, and restart from Step 1.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.disasm.dev/datadome/slider-captcha.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
