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, depending on your architecture:

  1. Direct HTTP — you give the solver a target URL (and, for DataDome, the captured challenge payload); it fetches whatever it needs, runs the challenge in its sandbox, and optionally submits the result for you. See POST /akamai/solve and POST /dd/solve.
  2. MITM proxy hand-off — if you already run a man-in-the-middle proxy in front of your bots (to intercept 403/challenge responses), hand the captured challenge straight to the solver instead of re-fetching it. See POST /akamai/mitm-solve and POST /dd/mitm-solve.

Akamai additionally exposes a stateful WebSocket session (/akamai/session) for browser-automation setups (Playwright, Puppeteer) where you want to relay each sensor submission through the bot's own browser context rather than have the solver make the request itself.

Full request/response contracts:

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>" }

See the DataDome API reference for the full challenge shape, interstitial handling, and submit=false capture mode.

Client integration flow (browser-automation / WebSocket)

For bots driven by a real browser (Playwright, Puppeteer, etc.), the WebSocket flow lets you keep the browser's own cookie jar and TLS fingerprint in the loop:

  1. Launch a browser and navigate to the target URL.
  2. Capture the challenge — intercept the Akamai sensor script source, grab the page HTML, and collect current cookies.
  3. Connect to ws://host:3000/akamai/session.
  4. Send init with the captured script, HTML, cookies, and URL.
  5. Relay each submission as a real XHR/fetch in the browser and reply with submission_response (status, body, updated cookies).
  6. Watch for cookie_update with accepted: true — the _abck cookie is now valid.
  7. Close the socket and continue browsing with the accepted cookies.

Full message schemas are in the Akamai API reference.