Handle CAPTCHA task results safely in TypeScript

By solvethree3 min read

Switch on result.status before reading result.solution. Only ready contains a usable solution, and even then you must check the fields your requested product needs: Kasada returns headers, while reCAPTCHA and TMPT return tokens with different uses.

On this page

Narrow the status before reading the solution

Use the SDK’s exported TaskRequest for input and TaskResult for responses. Narrowing the status lets TypeScript distinguish unfinished work, failure, and readiness without any or a cast that merely tells the compiler to trust you.

TypeScript · narrow the task status before using a solution
import { Solvethree, type TaskRequest, type TaskResult } from '@solvethree/sdk';
const client = new Solvethree({ apiKey: process.env.SOLVETHREE_API_KEY! });
const input: TaskRequest = { site: 'accounts.example.com', action: 'login', proxy: process.env.SOLVETHREE_PROXY! };
function inspect(result: TaskResult): void {
  switch (result.status) {
    case 'pending':
    case 'solving':
      console.log('Retrieve this task again:', result.id);
      return;
    case 'failed':
      console.error('Failed task:', result.id, result.errorCode);
      return;
    case 'ready':
      // Solution fields depend on the requested product; check optional fields.
      if (!result.solution.token || !result.solution.userAgent || !result.solution.secChUa) {
        throw new Error('Expected a complete reCAPTCHA solution');
      }
      console.log('Ready task:', result.id);
      // Submit token privately with its browser identity and original proxy.
      return;
  }
}
inspect(await client.createTask(input, { waitMs: 0 }));
// Save the returned ID and call getTask while pending/solving with a deadline.

The example creates one task and inspects the immediate response. pending and solving mean retrieve it again; failed stops processing; ready reaches the solution checks. If you extract the ready branch into a helper, use the exported ReadyTask type for its parameter. That narrows the status, but it doesn't make every product-specific solution field mandatory.

The guards check for a token and both browser-identity fields because this input requests reCAPTCHA. They don't prove that a token will be accepted by the target. The example deliberately doesn't print the token.

  • Use Node.js 22+ and the official SDK tarball installation documented in the API docs; don't assume an npm registry release exists.
  • Set SOLVETHREE_API_KEY and SOLVETHREE_PROXY in the server environment. The ! assertions don't validate missing environment variables at runtime.
  • Replace accounts.example.com and login with the intended hostname and action. The site must already have an active workspace profile; see site profile setup.

Poll the saved ID, not a new task

The example leaves polling to the caller. Keep creation outside that loop: calling createTask again isn't how you check whether the first task has finished.

  1. 1.

    Save the creation result’s ID

    Check that the returned ID is a nonempty string before using it for retrieval. Keep it associated with the original request, which you'll also need when selecting the successful proxy.

  2. 2.

    Retrieve that same task

    Call getTask with the saved ID while the status is pending or solving, with a local deadline. Before processing each response, check that its ID matches the saved ID. A matching ID establishes which task you're handling, not whether it succeeded.

  3. 3.

    Stop at a terminal result

    On ready, validate the fields required by the requested product. On failed, inspect both errorCode and error, then stop polling that task. Handle request errors separately: an unsuccessful retrieval request isn't itself a terminal task failure.

If creation times out before you receive an ID, don't assume no task was created. If you supplied a requestId, resubmitting the identical request with that same value recovers the existing task. Reusing it with changed inputs conflicts; generating a new value doesn't recover the original task.

A ready result still needs the right product handler

Choose the handler from the original request, then check the expected solution fields. A token’s presence alone can't distinguish a reCAPTCHA response from a TMPT cookie value.

What to consume after narrowing to ready
What to consume after narrowing to ready
Requested productSolution valueHow to use it
reCAPTCHA Enterprisesolution.tokenSubmit as the reCAPTCHA token for the intended hostname and action.
KasadaTasksolution.headersApply the returned headers together. Don't treat this result as a token.
TicketmasterTmptTasksolution.tokenUse as the raw tmpt cookie value, not as a reCAPTCHA token.

Standalone Kasada currently supports HTTPS targets on www.ticketmaster.com, not arbitrary sites. Its headers handler isn't a universal Kasada integration.

For each product, submit with the returned userAgent and secChUa and the successful proxy. With proxy candidates, use successfulProxy(originalTask, readyResult) rather than assuming the first candidate won. The SDK doesn't apply the solution to the target for you, and ready doesn't guarantee acceptance of that subsequent request.

Run your first task.

Start with $5 in workspace credit.

Create a workspace