Captcha solving service blog | SolveCaptcha
How to automatically solve slider captcha with Puppeteer
How to bypass slider captcha with Puppeteer
Slider captcha requires a user to drag a handle until a puzzle piece reaches the correct position. In browser automation, moving the slider is simple. The main challenge is identifying the target position and converting it into an accurate drag distance.
This guide explains how to solve custom slider captcha with Puppeteer and the [SolveCaptcha API](/captcha-solver-api). The script captures the captcha image, sends it to SolveCaptcha, receives the target coordinates, calculates the required offset, and moves the slider inside the active browser session.
Use this method only on websites you own or have permission to automate.
## What is slider captcha?
Slider captcha is an interactive verification challenge that normally contains:
- a background image;
- a missing puzzle area;
- a movable puzzle piece;
- a slider handle;
- client-side movement tracking;
- server-side verification of the final position.
The user must drag the puzzle piece until it aligns with the missing area.
Some implementations validate only the final offset. More advanced slider captcha systems may also analyze:
- movement duration;
- cursor acceleration;
- intermediate points;
- vertical deviation;
- pauses during movement;
- browser cookies;
- session data;
- IP reputation.
For this reason, directly changing the slider position through JavaScript may not work. Puppeteer should generate normal mouse movement events.
## Can Puppeteer solve slider captcha automatically?
Puppeteer can control the browser and perform the drag action, but it cannot reliably identify the missing puzzle position by itself.
Puppeteer handles:
1. Opening the target page.
2. Finding the captcha image.
3. Capturing the image.
4. Locating the slider handle.
5. Pressing and holding the mouse button.
6. Moving the pointer.
7. Releasing the slider.
8. Checking whether verification succeeded.
SolveCaptcha handles image recognition. The captcha image is submitted through the Coordinates method, and the API returns the point that Puppeteer should use to calculate the drag distance.
## How coordinate-based slider captcha solving works
For most custom puzzle sliders, the script needs two points:
- **Start point:** the center of the slider handle.
- **Target point:** the center of the missing puzzle area.
The start point is calculated from the slider element’s bounding box.
The target point is returned by SolveCaptcha after the service analyzes the submitted image.
The basic calculation is:
```text
Drag distance = target X coordinate - initial puzzle-piece center
```
Example:
```text
Target puzzle center: 184 px
Initial puzzle-piece center: 20 px
Required drag distance: 164 px
```
Puppeteer then moves the slider approximately 164 CSS pixels to the right.
The exact calculation may depend on:
- original image dimensions;
- rendered CSS dimensions;
- device pixel ratio;
- initial puzzle-piece position;
- slider-handle position;
- borders and padding;
- CSS transforms.
A fixed offset from one slider captcha should not be reused for another implementation.
## SolveCaptcha Coordinates API
The integration uses two endpoints.
Create a task:
```text
POST https://api.solvecaptcha.com/in.php
```
Get the result:
```text
GET https://api.solvecaptcha.com/res.php
```
The captcha image can be sent as a file or as a base64-encoded string.
For Puppeteer, base64 is convenient because a canvas or image element can be extracted without saving it to disk.
Example task parameters:
```text
key=YOUR_API_KEY
method=base64
coordinatescaptcha=1
body=BASE64_IMAGE
textinstructions=Click the center of the missing puzzle slot
json=1
```
Successful task creation response:
```json
{
"status": 1,
"request": "2122988149"
}
```
The `request` value is the task ID.
Request the result after approximately five seconds:
```text
https://api.solvecaptcha.com/res.php?key=YOUR_API_KEY&action=get&id=2122988149&json=1
```
While the task is being processed, the API returns:
```json
{
"status": 0,
"request": "CAPCHA_NOT_READY"
}
```
Completed coordinate response:
```json
{
"status": 1,
"request": "coordinate:x=184,y=76"
}
```
The upper-left corner of the submitted image is:
```text
x=0, y=0
```
For a typical slider captcha, the returned `x` value identifies the horizontal position of the missing puzzle area.
## Requirements
Use Node.js 20 or newer.
Create a project:
```bash
mkdir puppeteer-slider-captcha
cd puppeteer-slider-captcha
npm init -y
npm install puppeteer
```
Add ES module support to `package.json`:
```json
{
"type": "module"
}
```
Set the SolveCaptcha API key.
Linux or macOS:
```bash
export SOLVECAPTCHA_API_KEY="YOUR_API_KEY"
```
Windows PowerShell:
```powershell
$env:SOLVECAPTCHA_API_KEY="YOUR_API_KEY"
```
Do not store the API key directly in public source code.
## Complete Puppeteer example
Create a file named `index.js`:
```js
import puppeteer from 'puppeteer';
const CREATE_TASK_URL = 'https://api.solvecaptcha.com/in.php';
const GET_RESULT_URL = 'https://api.solvecaptcha.com/res.php';
const TARGET_URL =
'https://www.jqueryscript.net/demo/image-puzzle-slider-captcha/';
const API_KEY = process.env.SOLVECAPTCHA_API_KEY;
const MAX_ATTEMPTS = 3;
const POLL_INTERVAL_MS = 5_000;
const SOLVE_TIMEOUT_MS = 120_000;
/*
* This value is specific to the demonstration captcha.
* The movable puzzle piece is approximately 40 pixels wide and
* initially starts at the left edge, so its center is around x=20.
*/
const INITIAL_PIECE_CENTER_X = 20;
if (!API_KEY) {
throw new Error(
'Set the SOLVECAPTCHA_API_KEY environment variable before running the script.'
);
}
const sleep = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
async function parseApiResponse(response) {
const responseText = await response.text();
if (!response.ok) {
throw new Error(
`SolveCaptcha HTTP error ${response.status}: ${responseText.slice(0, 500)}`
);
}
try {
return JSON.parse(responseText);
} catch {
throw new Error(
`SolveCaptcha returned invalid JSON: ${responseText.slice(0, 500)}`
);
}
}
async function createCoordinateTask(base64Image) {
const body = new URLSearchParams({
key: API_KEY,
method: 'base64',
coordinatescaptcha: '1',
body: base64Image,
textinstructions: 'Click the center of the missing puzzle slot',
json: '1',
});
const response = await fetch(CREATE_TASK_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
});
const result = await parseApiResponse(response);
if (result.status !== 1) {
throw new Error(
`SolveCaptcha task creation failed: ${result.request ?? 'UNKNOWN_ERROR'}`
);
}
return String(result.request);
}
function parseCoordinates(answer) {
const coordinates = [];
const pattern =
/coordinate:x=(-?\d+(?:\.\d+)?),y=(-?\d+(?:\.\d+)?)/g;
for (const match of answer.matchAll(pattern)) {
coordinates.push({
x: Number(match[1]),
y: Number(match[2]),
});
}
if (coordinates.length === 0) {
throw new Error(`Unexpected coordinate response: ${answer}`);
}
return coordinates;
}
async function getCoordinateResult(taskId) {
const url = new URL(GET_RESULT_URL);
url.search = new URLSearchParams({
key: API_KEY,
action: 'get',
id: taskId,
json: '1',
}).toString();
const response = await fetch(url);
const result = await parseApiResponse(response);
if (result.status === 1) {
return parseCoordinates(String(result.request));
}
if (result.request === 'CAPCHA_NOT_READY') {
return null;
}
throw new Error(
`SolveCaptcha task ${taskId} failed: ${result.request ?? 'UNKNOWN_ERROR'}`
);
}
async function solveCoordinates(base64Image) {
const taskId = await createCoordinateTask(base64Image);
const deadline = Date.now() + SOLVE_TIMEOUT_MS;
while (Date.now() < deadline) {
await sleep(POLL_INTERVAL_MS);
const coordinates = await getCoordinateResult(taskId);
if (coordinates !== null) {
return {
taskId,
coordinates,
};
}
}
throw new Error(
`SolveCaptcha task ${taskId} exceeded the ${
SOLVE_TIMEOUT_MS / 1000
}-second timeout.`
);
}
async function reportTask(taskId, accepted) {
const url = new URL(GET_RESULT_URL);
url.search = new URLSearchParams({
key: API_KEY,
action: accepted ? 'reportgood' : 'reportbad',
id: taskId,
json: '1',
}).toString();
try {
const response = await fetch(url);
await parseApiResponse(response);
} catch (error) {
console.error(
`Unable to report task ${taskId}:`,
error instanceof Error ? error.message : error
);
}
}
async function captureCaptchaImage(page) {
const canvas = await page.waitForSelector('#captcha canvas, canvas', {
timeout: 15_000,
});
if (!canvas) {
throw new Error('Captcha canvas was not found.');
}
const boundingBox = await canvas.boundingBox();
if (!boundingBox) {
throw new Error('Unable to calculate the captcha canvas position.');
}
try {
const canvasData = await canvas.evaluate((element) => ({
dataUrl: element.toDataURL('image/png'),
pixelWidth: element.width,
pixelHeight: element.height,
}));
return {
base64Image: canvasData.dataUrl.replace(
/^data:image\/[a-zA-Z0-9.+-]+;base64,/,
''
),
pixelWidth: canvasData.pixelWidth,
pixelHeight: canvasData.pixelHeight,
boundingBox,
};
} catch {
/*
* Canvas extraction can fail when a cross-origin image
* makes the canvas unavailable to toDataURL().
*/
const screenshot = await canvas.screenshot();
return {
base64Image: Buffer.from(screenshot).toString('base64'),
pixelWidth: boundingBox.width,
pixelHeight: boundingBox.height,
boundingBox,
};
}
}
async function getSliderCenter(page) {
const slider = await page.waitForSelector(
'#captcha .slider, #captcha div.slider, div.slider',
{
timeout: 15_000,
visible: true,
}
);
if (!slider) {
throw new Error('Slider handle was not found.');
}
const boundingBox = await slider.boundingBox();
if (!boundingBox) {
throw new Error('Unable to calculate the slider position.');
}
return {
x: boundingBox.x + boundingBox.width / 2,
y: boundingBox.y + boundingBox.height / 2,
};
}
async function dragSlider(page, start, target) {
await page.mouse.move(start.x, start.y);
await page.mouse.down();
await page.mouse.move(target.x - 5, target.y + 1, {
steps: 35,
});
await page.mouse.move(target.x + 2, target.y - 1, {
steps: 8,
});
await page.mouse.move(target.x, target.y, {
steps: 5,
});
await page.mouse.up();
}
async function attemptSliderCaptcha(page) {
const captcha = await captureCaptchaImage(page);
const start = await getSliderCenter(page);
const { taskId, coordinates } = await solveCoordinates(
captcha.base64Image
);
const targetPoint = coordinates[0];
/*
* SolveCaptcha returns coordinates in image pixels.
* Puppeteer moves the mouse in rendered CSS pixels.
*/
const scaleX =
captcha.boundingBox.width / captcha.pixelWidth;
const dragDistance =
(targetPoint.x - INITIAL_PIECE_CENTER_X) * scaleX;
const target = {
x: start.x + dragDistance,
y: start.y,
};
console.log({
taskId,
targetPoint,
scaleX,
dragDistance,
start,
target,
});
const previousUrl = page.url();
const navigationPromise = page
.waitForNavigation({
timeout: 8_000,
waitUntil: 'domcontentloaded',
})
.catch(() => null);
await dragSlider(page, start, target);
const navigationResponse = await navigationPromise;
const urlChanged = page.url() !== previousUrl;
const accepted = navigationResponse !== null || urlChanged;
await reportTask(taskId, accepted);
return accepted;
}
async function main() {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: {
width: 1280,
height: 900,
},
});
const page = await browser.newPage();
try {
await page.goto(TARGET_URL, {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
for (
let attempt = 1;
attempt <= MAX_ATTEMPTS;
attempt += 1
) {
console.log(`Slider captcha attempt ${attempt}/${MAX_ATTEMPTS}`);
try {
const solved = await attemptSliderCaptcha(page);
if (solved) {
console.log('Slider captcha was accepted.');
await page.screenshot({
path: 'slider-captcha-solved.png',
fullPage: true,
});
return;
}
console.log('The slider position was rejected.');
} catch (error) {
console.error(
`Attempt ${attempt} failed:`,
error instanceof Error ? error.message : error
);
}
if (attempt < MAX_ATTEMPTS) {
await page.goto(TARGET_URL, {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
await sleep(1_500);
}
}
throw new Error(
`Unable to solve the slider captcha after ${MAX_ATTEMPTS} attempts.`
);
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(
error instanceof Error ? error.stack : error
);
process.exitCode = 1;
});
```
Run the script:
```bash
node index.js
```
After successful verification, the script saves:
```text
slider-captcha-solved.png
```
## How the offset calculation works
Suppose SolveCaptcha returns:
```text
coordinate:x=184,y=76
```
The center of the missing puzzle area is located at approximately `x=184`.
If the movable puzzle piece is 40 pixels wide and starts at the left edge, its initial center is:
```text
40 / 2 = 20 px
```
The required image-space movement is:
```text
184 - 20 = 164 px
```
The source image may be rendered at a different CSS size. The script therefore calculates:
```text
scaleX = rendered canvas width / source image width
```
The browser drag distance becomes:
```text
dragDistance =
(targetX - initialPieceCenterX) * scaleX
```
This conversion is important for:
- responsive layouts;
- high-DPI displays;
- scaled canvas elements;
- browser zoom;
- CSS transforms;
- mobile device emulation.
## How to adapt the script to another slider captcha
Slider captcha implementations are different. You must inspect and update several values.
### Captcha image selector
The example uses:
```js
'#captcha canvas, canvas'
```
Other possible selectors include:
```js
'.slider-captcha canvas'
'.captcha-image'
'img[data-role="captcha-background"]'
'.geetest_canvas_bg'
```
Capture only the captcha image.
Do not submit:
- a full-page screenshot;
- navigation elements;
- buttons;
- unrelated text;
- browser controls.
The coordinates returned by SolveCaptcha are relative to the submitted image.
### Slider handle selector
The example uses:
```js
'#captcha .slider, #captcha div.slider, div.slider'
```
Other implementations may use:
```js
'.slider-button'
'.drag-handle'
'.captcha-slider'
'.geetest_slider_button'
```
Select the draggable handle rather than the entire slider track.
### Initial puzzle-piece position
The example uses:
```js
const INITIAL_PIECE_CENTER_X = 20;
```
This value is correct only when the puzzle piece:
- is approximately 40 pixels wide;
- starts at the left edge.
For another implementation, calculate:
```text
Initial center =
initial piece left position + piece width / 2
```
Example:
```text
Initial left position: 8 px
Piece width: 44 px
Initial center = 8 + 44 / 2 = 30 px
```
Then update the configuration:
```js
const INITIAL_PIECE_CENTER_X = 30;
```
### Success detection
The demonstration page redirects after successful verification.
A production website may instead:
- hide the captcha;
- add a success class;
- enable a form button;
- update a hidden input;
- send an XHR request;
- replace the captcha with a success message.
Example for a success element:
```js
const accepted = await page
.waitForSelector('.captcha-success', {
timeout: 8_000,
visible: true,
})
.then(() => true)
.catch(() => false);
```
Example for a hidden input:
```js
const accepted = await page.waitForFunction(() => {
const input = document.querySelector(
'input[name="captcha_verified"]'
);
return input?.value === '1';
});
```
Use the Network and Elements panels to identify the website’s actual success condition.
## Capturing canvas-based captcha
Many slider captcha widgets render the background through an HTML canvas.
The preferred extraction method is:
```js
const dataUrl = await page.evaluate(() => {
const canvas = document.querySelector(
'.slider-captcha canvas'
);
return canvas.toDataURL('image/png');
});
```
This preserves the original image dimensions.
Canvas extraction may fail when the image comes from another domain without appropriate CORS headers. In this case, the browser marks the canvas as tainted.
Use an element screenshot as a fallback:
```js
const canvas = await page.$('.slider-captcha canvas');
const image = await canvas.screenshot();
const base64 = Buffer.from(image).toString('base64');
```
When using a screenshot, make sure the returned coordinate dimensions match the rendered element dimensions.
## When one coordinate is not enough
The Coordinates method works when the missing area can be identified from one image.
Some slider captcha implementations may require:
- a separate background image;
- a separate puzzle-piece image;
- reconstruction of a scrambled image;
- multiple canvas layers;
- provider-specific challenge parameters;
- signed verification requests;
- encrypted movement data;
- a token tied to the browser session.
In such cases, inspect:
1. DOM elements.
2. Canvas layers.
3. Image requests.
4. Challenge initialization requests.
5. Verification requests.
6. JavaScript configuration values.
7. Image and slider dimensions.
The Coordinates method identifies a point in an image. It does not automatically generate provider-specific signatures or encrypted verification parameters.
## Handling multiple coordinates
SolveCaptcha may return more than one point:
```text
coordinate:x=39,y=59;x=252,y=72
```
The parser converts the response into:
```js
[
{ x: 39, y: 59 },
{ x: 252, y: 72 },
]
```
A standard puzzle slider normally requires one point.
Multiple coordinates may mean:
- the instruction was ambiguous;
- the image contains several possible targets;
- the captcha requires multiple clicks;
- the wrong solving method was selected.
Log and inspect all returned points instead of silently ignoring additional values.
## Error handling
A reliable integration must handle API failures, browser failures, and rejected slider positions separately.
### CAPCHA_NOT_READY
The task is still being processed.
Correct behavior:
- keep the same task ID;
- wait five seconds;
- request the result again;
- stop after a defined timeout.
Do not create duplicate tasks for the same image.
### ERROR_CAPTCHA_UNSOLVABLE
SolveCaptcha could not identify the correct position.
Correct behavior:
1. Refresh the captcha.
2. Capture the new image.
3. Create a new task.
4. Limit the number of retries.
Do not repeatedly submit the same image.
### ERROR_WRONG_USER_KEY
The API key is invalid.
Stop the script and verify:
```text
SOLVECAPTCHA_API_KEY
```
### ERROR_ZERO_BALANCE
The account does not have enough balance.
Stop creating new tasks until the balance is replenished.
### ERROR_NO_SLOT_AVAILABLE
The task cannot currently be accepted.
Use delayed retries or exponential backoff. Do not continuously resend requests.
### Selector timeout
A selector timeout may mean:
- the page structure changed;
- the captcha did not load;
- the element is inside an iframe;
- a cookie dialog blocks the page;
- another action is required to display the captcha.
Inspect the active page before retrying.
### Incorrect slider position
If the slider consistently stops too early or too late, verify:
- `INITIAL_PIECE_CENTER_X`;
- image-to-CSS scaling;
- canvas borders;
- page zoom;
- device pixel ratio;
- slider-track coordinates;
- whether the returned point represents a center or edge;
- whether the submitted image was cropped.
Use screenshots and coordinate logs during calibration.
## Reporting correct and incorrect answers
When the website accepts the returned coordinate, send:
```text
action=reportgood
```
If the coordinate itself was incorrect, send:
```text
action=reportbad
```
Correct answer report:
```text
https://api.solvecaptcha.com/res.php?key=YOUR_API_KEY&action=reportgood&id=2122988149
```
Incorrect answer report:
```text
https://api.solvecaptcha.com/res.php?key=YOUR_API_KEY&action=reportbad&id=2122988149
```
Do not report a solution as incorrect when the failure was caused by:
- an outdated captcha image;
- a wrong selector;
- incorrect coordinate scaling;
- an expired session;
- a network failure;
- an incorrect initial offset;
- releasing the slider too early;
- an incorrect success check.
Use `reportbad` only when the returned coordinate was wrong.
## Improving reliability in scraping workflows
Slider solving should be one stage of a larger browser automation pipeline.
### Preserve the browser session
Keep related requests in the same browser context so cookies, local storage, and session values remain consistent.
### Avoid unnecessary reloads
Reloading the page may generate a new captcha and invalidate the image already submitted to SolveCaptcha.
### Wait for stable rendering
Before capturing the image, confirm that:
- the canvas is visible;
- its dimensions are greater than zero;
- images have finished loading;
- animations have stopped;
- the slider handle is active.
### Keep the result synchronized with the image
A coordinate result is valid only for the exact image submitted to the API.
If the captcha refreshes while SolveCaptcha is processing the task:
1. Discard the old result.
2. Capture the new image.
3. Create a new task.
### Log each stage
Useful values to log include:
- page URL;
- captcha image dimensions;
- rendered dimensions;
- SolveCaptcha task ID;
- returned coordinates;
- scale factor;
- calculated drag distance;
- start pointer position;
- target pointer position;
- verification result;
- retry number.
These logs help distinguish image-recognition errors from Puppeteer integration errors.
## Testing slider captcha on your own website
For automated testing of an application you control, solving a production captcha during every test is usually unnecessary.
Consider using:
- a test-mode captcha configuration;
- fixed challenges in staging;
- mocked verification responses;
- a test-only feature flag;
- separate captcha integration tests;
- a limited production smoke test.
This keeps normal application tests predictable while still testing the real captcha integration separately.
## Frequently asked questions
### Can Puppeteer drag a slider?
Yes. Puppeteer provides mouse movement, button press, and button release methods that can reproduce a slider drag.
### Can Puppeteer identify the correct target position?
Not reliably by itself. The target must be found through image analysis, challenge data, or a captcha-solving service such as SolveCaptcha.
### Which SolveCaptcha method is used?
Use the Coordinates method when the missing puzzle position can be identified from an image.
Set:
```text
coordinatescaptcha=1
```
### Why is the returned coordinate not equal to the drag distance?
SolveCaptcha returns a point relative to the submitted image.
Puppeteer requires a movement distance relative to the slider handle and the puzzle piece’s initial position.
These values use different coordinate origins.
### Why does the slider stop in the wrong place?
Common causes include:
- incorrect initial puzzle-piece center;
- incorrect CSS scaling;
- cropped image;
- browser zoom;
- device pixel ratio;
- borders or padding;
- stale captcha image;
- interpreting the returned point as an absolute browser coordinate.
### Should the slider move in one step?
No. A single instantaneous movement may not produce the sequence of mouse events expected by the captcha widget.
Use several intermediate movements and release the mouse only at the target.
### Can I submit a full-page screenshot?
It is possible but not recommended.
The returned coordinate would be relative to the full screenshot instead of the captcha image, making the calculation more difficult and less accurate.
### Do all slider captcha widgets use the same selectors?
No. Image elements, canvas structure, slider handles, puzzle-piece positions, and success conditions differ between implementations.
## Conclusion
Automatic slider captcha solving requires both image recognition and browser control.
SolveCaptcha identifies the target point in the captcha image. Puppeteer converts that point into a drag operation inside the active browser session.
A reliable workflow should:
1. Capture only the captcha image.
2. Submit it with `coordinatescaptcha=1`.
3. Poll the result every five seconds.
4. Convert image pixels to CSS pixels.
5. Subtract the puzzle piece’s initial position.
6. Drag the slider with Puppeteer.
7. Verify the actual success state.
8. Report accepted or rejected answers.
9. Retry only with a fresh captcha image.
Use the [SolveCaptcha slider captcha solver](/captcha-solver/slider-captcha-solver-bypass) for custom puzzle challenges and the [SolveCaptcha API documentation](/captcha-solver-api) for complete request parameters and error codes.