Playwright / browser
Use this when the site needs a real browser anyway — a SPA, a login flow, or content you can't get from raw HTML. If all you want is a clearance cookie, HTTP clients are far cheaper.
Why go through the browser
The point isn't that solving works better here — it's who sends the submission. In this approach Chrome owns the actual request: the native interstitial POST, the captcha GET, or the Akamai sensor XHR. That means it carries a genuine Chrome TLS fingerprint and the browser's own cookie jar, which some sites check independently of the challenge itself.
That property is the reason to accept the cost. See solving ≠ staying unblocked.
DataDome
Attach to a Playwright page, let the helper watch for a challenge, and it resolves once DataDome returns an accepted cookie. It handles the interstitial → captcha escalation for you.
import { chromium } from 'playwright';
import { solve } from '#src/datadome/solver.js';
const browser = await chromium.launch();
const context = await browser.newContext({ proxy: { server: proxy } });
const page = await context.newPage();
const result = await solve(page, { proxy, solverApiKey, solverUrl, url });
// -> { cookie, responseStatus, url }
// the page is now cleared; carry on driving it normally
await page.goto('https://www.grainger.com/category/…');Under the hood it watches for the challenge, asks your container for the sensor values via POST /dd/solve, and hands the result back to the page so Chrome performs the submission.
Runnable version: src/datadome/grainger.ts in xhrdev/examples.
Akamai — the WebSocket session
Akamai works differently. Rather than one request/response, the solver keeps a stateful session open and streams you one sensor submission per round, each of which you relay through the browser and report back on.
import { solve } from '#src/akamai/solver.js';
await solve(page, { proxy, solverApiKey, solverUrl, timeout: 120_000, url });Runnable versions: src/akamai/comcast.ts and src/akamai/ca-edd.ts.
The protocol
If you're implementing this yourself rather than using the example helper:
- Launch a browser and navigate to the target URL.
- Capture the challenge — intercept the Akamai sensor script source, grab the page HTML, and collect current cookies.
- Connect to
ws://host:3000/akamai/session. - Send
initwith the captured script, HTML, cookies, and URL. - Relay each
submissionas a real XHR/fetch in the browser, and reply withsubmission_responsecarrying the status, body, and updated cookies. - Watch for
cookie_updatewithaccepted: true— the_abckcookie is now valid. - Close the socket and continue browsing with the accepted cookies.
Full message schemas, session TTL, and submission timeouts are in the Akamai API reference.
Session limits
A session expires after 5 minutes of inactivity (each submission_response resets the timer), and each individual submission must be answered within 30 seconds.
Keeping the identity consistent
The browser and the profile you send the solver have to agree. If you launch Chrome with one user agent and tell the solver something else, the solve gets rejected in a way that doesn't point at the cause.
The examples share a single profile.ts between the browser launch options and the solver payload for exactly this reason — worth copying that structure.
Cost
Be realistic about what this costs relative to the HTTP path: a browser process per session, plus the page loads. If you're running this at volume, pool contexts rather than launching a browser per job, and reserve the browser approach for the targets that actually need it.
Next
- Using HTTP clients — the cheaper path
- Akamai API reference · DataDome API reference