Use the CAPTCHA solver in Express
Keep the solvethree client on your Express server. Use an authenticated POST route to create a task and a GET route to retrieve it. Fix the target and proxy in server configuration so callers can’t turn the endpoint into an arbitrary paid relay.
On this page
Configure the server and target
Use Node.js 22 or later and Express 5. Install the official JavaScript SDK using the tarball installation in the documentation, rather than assuming it’s published in the npm registry. The example creates one Solvethree client outside the handlers.
SOLVETHREE_API_KEY: your workspace API key. Keep it on the server.INTEGRATION_KEY: a separate secret for trusted internal callers. The example requires it in thex-integration-keyheader; don’t distribute it to browser clients. If you already have authentication middleware, use that instead of the example’s shared-key guard.CAPTCHA_SITE: the target hostname, not a full URL. Set up an active site profile for your workspace before creating tasks; sending a hostname doesn’t create one.SOLVETHREE_PROXY: the proxy for this target. The example supplies a single proxy and fixes the action tologin. Change that action server-side if your target uses a different one.
This request uses the default reCAPTCHA Enterprise configuration. It needs site, action, and a proxy—not type or url. See site profile setup if the target isn’t configured yet.
Create once, then retrieve the task
// npm install express and the official solvethree SDK tarball.
import express from 'express';
import { Solvethree, SolvethreeError } from '@solvethree/sdk';
const app = express();
const client = new Solvethree({ apiKey: process.env.SOLVETHREE_API_KEY });
const secret = process.env.INTEGRATION_KEY;
if (!secret) throw new Error('Set INTEGRATION_KEY');
app.use((request, response, next) => {
if (request.get('x-integration-key') !== secret) return response.sendStatus(401);
next();
});
app.post('/captcha/tasks', async (request, response) => {
const task = await client.createTask({
site: process.env.CAPTCHA_SITE, action: 'login', proxy: process.env.SOLVETHREE_PROXY,
requestId: request.get('x-request-id'),
}, { waitMs: 0, signal: AbortSignal.timeout(20000) });
response.json(task);
});
app.get('/captcha/tasks/:id', async (request, response) => {
response.json(await client.getTask(request.params.id, { waitMs: 15000, signal: AbortSignal.timeout(25000) }));
});
app.use((error, request, response, next) => {
if (error instanceof SolvethreeError) return response.status(502).json({ requestId: error.requestId, taskId: error.taskId });
response.status(400).json({ error: 'Invalid task input or ID' });
});
app.listen(3000);Both routes sit behind the authentication guard. The POST handler takes the hostname, action, and proxy from server configuration, not the request body. Only the optional creation identifier comes from the caller.
- 1.
Assign a request ID before creation
Send
x-request-idwith 1–64 ASCII letters, numbers, underscores, or hyphens. Keep it with the logical request so you can recover creation if the connection times out. - 2.
POST to
/captcha/tasksonceThe handler calls
createTaskwithwaitMs: 0and returns the task. Keep its ID. A successful response means you have a task, not necessarily a solution. - 3.
Retrieve status with GET
Call
/captcha/tasks/:id. Continue retrieving while the status ispendingorsolving; don’t repeat POST to check progress. Stop onreadyorfailed. - 4.
Use only a ready solution
Submit the token with the returned
userAgentandsecChUa, using the same configured proxy and intended target. The SDK doesn’t submit it for you. A ready task means a solution was generated, not that the target accepted your application request.
Handle timeouts without creating another task
The POST SDK call has a 20-second AbortSignal deadline. GET allows a 15-second task wait inside a 25-second SDK-call deadline. A GET response can still be nonterminal; inspect its status rather than treating the end of the wait as a failed solve.
A creation timeout doesn’t prove that solvethree rejected the request. If you received a task ID, retrieve it. Otherwise, repeat creation with the same request ID and unchanged task inputs to recover the existing task. Reusing that ID with different inputs conflicts. Without a retained request ID, a new POST risks creating separate work.
Express 5 forwards rejected async handlers to the error middleware. The example returns HTTP 502 for SolvethreeError, exposing only its request and task identifiers—not the raw error, credentials, or solution. Its fallback HTTP 400 is generic; it doesn’t precisely classify every non-SDK error, including deadline errors.
Handle HTTP errors before reading a success payload. Separately, a retrieved task can have status failed: inspect its errorCode and error, then stop polling. Failed solves are refunded, but retrying unchanged invalid inputs won’t fix them. Use the API error reference to diagnose the reported failure.
Sources and API reference
Run your first task.
Start with $5 in workspace credit.