Use the Node.js SDK in AWS Lambda
Use Node.js 22 or later and bundle the official SDK with your handler. Set the SDK deadline below Lambda’s remaining execution time so the function has time to return task and error IDs.
On this page
Package the SDK and configure the target
- 1.
Install the dependency
Initialize an npm package in a clean build directory. Use npm to install the official tarball at
https://solvethree.com/downloads/solvethree-sdk-0.1.0.tgz. It provides the@solvethree/sdkimport; don’t assume there’s an npm registry release. - 2.
Configure the workspace and target
Set private Lambda environment variables named
SOLVETHREE_API_KEY,CAPTCHA_SITE, andSOLVETHREE_PROXY. Use a workspace API key, the target hostname rather than a full URL, and the proxy for this task. The workspace must already have an active profile for that site; see site profile setup. - 3.
Save and package the handler
Save the example below as
index.mjs. Create a ZIP withindex.mjs, the package metadata, and the installednode_modulesdirectory at the archive root—not inside an enclosing project directory. Don’t put credentials in the ZIP. - 4.
Deploy through your existing Lambda process
Use the Node.js 22 runtime and set the handler to
index.handler. Invoke it through your authorized backend or IAM flow, supplying a stablerequestIdfor the logical job. Keep the returned solution data private.
The example uses the action login; change it to the action your target expects before deploying. It sends one proxy and uses the default enterprise-v3 request type. Don’t add reCAPTCHA type or url fields.
Bound SDK waiting by the invocation budget
import { Solvethree, SolvethreeError, TaskFailedError, ApiError } from '@solvethree/sdk';
const client = new Solvethree({ apiKey: process.env.SOLVETHREE_API_KEY });
export const handler = async (event, context) => {
const timeoutMs = Math.min(180000, context.getRemainingTimeInMillis() - 1500);
if (timeoutMs < 1000) throw new Error('Not enough execution time to start a task');
// The caller supplies one stable ID for this logical job; Lambda retries must
// reuse it. awsRequestId changes with invocations and is not that logical ID.
if (!event.requestId) throw new Error('Provide a stable requestId');
try {
return await client.solve({
site: process.env.CAPTCHA_SITE, action: 'login', proxy: process.env.SOLVETHREE_PROXY,
requestId: event.requestId,
}, { timeoutMs, signal: AbortSignal.timeout(timeoutMs) });
} catch (error) {
if (error instanceof TaskFailedError) return { status: 'failed', id: error.taskId, errorCode: error.errorCode };
if (error instanceof ApiError) return { apiError: error.status, requestId: error.requestId };
if (error instanceof SolvethreeError) return { interrupted: true, requestId: error.requestId, taskId: error.taskId };
throw error;
}
};
// Invoke through your authorized backend/IAM flow. The return value is private
// solution data; do not attach this handler to an unauthenticated public URL.The handler reads Lambda’s remaining execution time, reserves 1,500 milliseconds, and caps SDK waiting at 180,000 milliseconds. That cap belongs to this example; it isn’t a universal Lambda execution limit. With less than one second left after the reserve, the handler refuses to start.
Both the SDK timeout and the abort signal use the calculated deadline. solve creates the task once and polls until it’s ready or an error occurs. The reserve gives error handling time to return recovery information, but a forced Lambda termination can still prevent any response.
The caller must retain event.requestId across invocation retries. Use 1–64 ASCII letters, numbers, underscores, or hyphens. Don’t use awsRequestId: it identifies an invocation, not the logical solve request.
Handle failures without starting another solve blindly
| Result | What to do |
|---|---|
A task with status ready | Use its solution. Task creation or a successful HTTP response alone isn’t enough. |
status: failed | Record id and errorCode. Retrieve the task if you need its error message, but stop polling that failed task. |
apiError | Record the HTTP status and returned request ID. This is an API request error, not a terminal task failure; inspect the cause before retrying unchanged inputs. |
interrupted: true | Keep any returned taskId and requestId, along with the caller’s original logical request ID, for recovery. |
Validation errors and unexpected errors are rethrown. The handler doesn’t turn every exception into an interruption response.
- 1.
Recover by task ID when available
Retrieve the existing task with
getTask.pendingandsolvingmean it’s still running; onlyreadycontains a usable solution. Keep any further waiting within the new invocation’s remaining budget. - 2.
Recover by logical request ID otherwise
Retry the same task request with the caller’s original
requestId. Matching inputs return the existing task; different inputs conflict. Keep the site, action, and proxy unchanged during recovery, including across configuration changes.
A connection timeout doesn’t prove task creation failed. Recover the existing task before assigning a new logical request ID. See task polling and API error handling for the underlying contracts.
Submit with the same proxy and returned browser identity
The SDK returns solution data; it doesn’t apply it to the target. When your application submits it, use the configured proxy and the returned userAgent and secChUa. Don’t substitute an unrelated browser identity or target URL. A ready task means a solution was generated—not that the target accepted the subsequent request.
Sources and API reference
Run your first task.
Start with $5 in workspace credit.