Use the Node.js SDK in AWS Lambda

By solvethree4 min read

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. 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/sdk import; don’t assume there’s an npm registry release.

  2. 2.

    Configure the workspace and target

    Set private Lambda environment variables named SOLVETHREE_API_KEY, CAPTCHA_SITE, and SOLVETHREE_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. 3.

    Save and package the handler

    Save the example below as index.mjs. Create a ZIP with index.mjs, the package metadata, and the installed node_modules directory at the archive root—not inside an enclosing project directory. Don’t put credentials in the ZIP.

  4. 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 stable requestId for 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

Lambda index.mjs · bound the SDK 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

How to interpret the handler’s return value
How to interpret the handler’s return value
ResultWhat to do
A task with status readyUse its solution. Task creation or a successful HTTP response alone isn’t enough.
status: failedRecord id and errorCode. Retrieve the task if you need its error message, but stop polling that failed task.
apiErrorRecord 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: trueKeep 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. 1.

    Recover by task ID when available

    Retrieve the existing task with getTask. pending and solving mean it’s still running; only ready contains a usable solution. Keep any further waiting within the new invocation’s remaining budget.

  2. 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.

Run your first task.

Start with $5 in workspace credit.

Create a workspace