Skip to content

Getting Started

xhr.dev is an on-prem anti-bot challenge solver. You run it as a Docker container inside your own infrastructure — your traffic, proxies, and target sites never touch xhr.dev's servers. We ship you a container image and a signed licence file; everything else runs on your hardware.

It currently solves challenges from:

  • Akamai Bot Manager — sensor challenges (_abck / bm-sz) and SBSD
  • DataDome — captcha and interstitial challenges

Don't see the provider you need? Message us.

How it fits into your stack

There are two ways to integrate — see How to integrate for which to pick:

  1. HTTP clients — no browser. Your code makes the requests (axios, undici, fetch, requests, …), hands the captured challenge to the solver, and sends the solved submission itself. Cheapest and fastest.
  2. Playwright / browser — when the site needs a real browser anyway. The solver attaches to your page and lets Chrome own the submission, so it carries a real TLS fingerprint and the browser's own cookie jar.

Read next:

Quickstart

You'll need a licence.json / licence.sig pair — contact us or book a call to get one issued.

bash
mkdir -p licence
# place licence.json and licence.sig from xhr.dev into ./licence

docker pull ghcr.io/xhrdev/xhrdev:latest

docker run -d \
  --name xhrdev \
  --restart unless-stopped \
  -p 3000:3000 \
  -v ./licence:/run/licence:ro \
  ghcr.io/xhrdev/xhrdev:latest

curl http://localhost:3000/hc
# {"status":"ok"}

If the licence is missing, expired, or tampered with, the container exits immediately with {"event":"launcher_failed"} and a non-zero exit code — it fails closed, not open. See the deployment guide for volume mount vs. environment-variable licence delivery, Docker Compose, and the full environment variable reference.

Solving an Akamai challenge (example)

bash
curl -X POST http://localhost:3000/akamai/solve \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://target.example.com/login",
    "profile": {
      "id": "chrome-146-macos",
      "chromeFullVersion": "146.0.7680.81",
      "os": "macos",
      "timezone": "America/New_York",
      "timezoneOffsetMinutes": -300,
      "tlsClientHello": "chrome_146",
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
      "httpHeaderTemplates": { "form": [], "iframe": [], "image": [], "xhr": [] }
    },
    "js_profile": {
      "os": "macos",
      "chromeVersion": "146",
      "hardware": { "webgl": { "vendor": "Google Inc. (Apple)", "renderer": "ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)" } },
      "screen": { "outerHeight": 1080, "innerHeight": 960 }
    }
  }'
json
{
  "success": true,
  "accepted": true,
  "cookie_header": "_abck=...",
  "cookies": { "_abck": "..." },
  "mode": "abck",
  "outcome": "accepted",
  "sensors_sent": 2
}

The full field reference, capture-only mode, and error shapes are in the Akamai API reference.

Solving a DataDome challenge (example)

bash
curl -X POST http://localhost:3000/dd/solve \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://target.example.com/",
    "dd": { "cid": "...", "hsh": "...", "rt": "c", "s": 1 },
    "ddCookie": "<datadome cookie value>",
    "profile": { "id": "chrome-146-macos", "...": "..." },
    "js_profile": { "...": "..." }
  }'
json
{ "cookie": "<new datadome cookie value>" }

/dd/solve returns a prepared submission for you to send — DataDome binds the clearance cookie to the IP that submits it, so the request has to come from you. See why you send the submission.

See the DataDome API reference for the full challenge shape and interstitial handling.

Next steps