Skip to main content
Surfsky exposes CAPTCHA commands through a page-level CDP session. They work in any browser you start, with no setup: choose a solver for the challenge, send the command, and verify the page state after solving.

Choose a method

Some solvers are free. The others are built in but paid separately: each of their solves is charged on top of browser time, as described in Charges. The Cost column below shows which is which. Support can vary by challenge version and site configuration. Test on your target page and check that the protected content loads.

Connect and solve

This Python example starts a browser, connects Playwright, and attempts a manual Turnstile solve. Install the packages and set your Surfsky credentials:
Replace TARGET_URL with the page you are testing. Save as solve.py:
Run it:
A successful attempt returns {"status": "success", "type": "turnstile", "solution": "<token>"}. If no challenge is present, the command fails with the CDP error No captcha detected on the page. The final API request stops the browser when the example exits. For JavaScript, start with the Playwright lifecycle and create the CDP session with await page.context().newCDPSession(page). The command parameters are the same.

With an SDK

The Python and TypeScript SDKs start the session and speak CDP themselves. Send the commands with browser.send. Install the Python or TypeScript SDK, set TARGET_URL and your credentials as above, and run:
The browser stops when the block exits. browser.send waits 60 seconds for a reply: for a solver whose timeout is longer, such as reCAPTCHA, start the browser with command_timeout=200 in Python (seconds) or commandTimeout: 200_000 in TypeScript (milliseconds). Events arrive through browser.cdp.on(event, handler). The handler gets the event’s fields and the page’s session id.

Detection

Captcha.detect looks at the current page once and says whether a captcha or a challenge page is on it, without solving anything. It needs no solver and is free:
The reply names what was found:
detected says whether a captcha a solver can act on is on the page, captcha_type is its type as Captcha.solve takes it, and selector is the element it was found by. challenge_page names the provider of a challenge page served in place of the one you asked for, cloudflare, datadome or perimeterx, with or without a captcha on it. On a page with neither, every field but detected: false is null. Detection sees open and closed shadow roots, the provider’s own iframe, and a widget inside a site’s own iframe. The same scan is what Captcha.solve without type and Captcha.autoSolve use to find a captcha, and a Captcha.detect call emits Captcha.detectionStarted and Captcha.detectionCompleted.

Manual solving

Captcha.solve waits for one attempt to finish:
Pass only the options listed in the selected solver’s guide. Solvers may ignore other fields even when the request accepts them. Results include status and type. Statuses include success, failed, timeout, and not_detected, which means there was nothing to solve, for example a widget that had already passed. On success, solution holds the answer when the solver has one, such as a token or the recognized text. Invalid parameters and detection failures raise CDP errors instead of returning a status object. Captcha.solve can be called right after navigation: a widget that is on the page but not drawn yet is waited for a few seconds. With no widget on the page it fails at once with No captcha detected on the page. A hidden or invisible widget is not a captcha: call Captcha.solve after the step that shows it, such as the form’s submit. A charged solve also returns usage, on failed and timeout too:
usage.cost_usd is what the solve was charged.

Automatic solving

Use Captcha.autoSolve to start background detection on the page, with the types to watch in types. Attach event listeners before starting it, then navigate:
The response {"status": "started"} confirms that background solving started. Wait for the expected page content or response to confirm completion, and set a timeout for that wait. types sets which types automatic mode watches:
  • Without types, the default set applies: recaptcha, geetest, turnstile, perimeterx, datadome and datadome_audio. On DataDome the slider (datadome) goes first; audio takes over after three slider failures in a row.
  • A list watches only the types in it. aws_waf and funcaptcha are not in the default set: list them to solve them automatically.
  • [] watches every type except recaptcha, geetest and aws_waf. It still includes the paid DataDome audio and FunCaptcha.
An unknown name fails with Invalid captcha type. types does not restrict a manual Captcha.solve. type restricts detection to one of the watched types; a type outside them fails with is not in types, and a type your account has no automatic solver for fails with is not available for this account, listed or not. Calling autoSolve again replaces the current background loop, types included; disconnecting the CDP client ends it. After three failed solves in a row of one type, the loop stops solving that type, sends a Captcha.solveFailed event with error, and keeps solving the others; with type set, the loop ends. A challenge that has already passed is skipped without events.

Events

Payload fields depend on the event. Captcha.solveCompleted and Captcha.solveFailed carry the same fields as a Captcha.solve result, usage included. A solve that ended in an error sends error and the type when known.

Charges

A paid solve is charged whatever its outcome, failed and timeout included: how hard a challenge gets depends mostly on the proxy and the browser session. The charge equals the solve’s usage.cost_usd and is recorded against the browser session. The amounts in the Cost column are typical for one solve, not fixed prices. Nothing is charged when the challenge passes on its own, such as a reCAPTCHA checkbox that passes without a challenge or a GeeTest widget that passes on its button. Automatic mode solves, and charges for, every type it watches, and the default set includes paid types. Pass your own types to control what is charged.

Troubleshooting

Use DevTools or Screencast to observe one attempt before increasing concurrency.