Call the CAPTCHA API from Cloudflare Workers

By solvethree3 min read

Use a Worker to call solvethree’s task API with server-side fetch. Keep the workspace API key in a secret binding, fix the target inputs in the Worker, and return task responses only to your authorized application caller.

On this page

Configure the target and secrets

First, activate the target site’s profile for your workspace in the console. Sending a hostname and action won’t create one. See site profile setup if that prerequisite isn’t complete.

  • SOLVETHREE_API_KEY: your workspace API key, stored as a Worker secret.
  • INTEGRATION_KEY: a separate secret your application caller must send in x-integration-key.
  • SOLVETHREE_PROXY: the proxy URL used for solving. Store it as a secret if it contains credentials.
  • CAPTCHA_SITE: the target hostname, not a full URL.

The example fixes the action to login and supplies one proxy. Change those deployed inputs to match your integration; don’t accept arbitrary target inputs from callers. With recaptchaType omitted, the API defaults to reCAPTCHA Enterprise v3.

Use the integration key between a trusted application caller and the Worker, not in publicly shipped browser code. It’s a shared credential, not per-user task authorization.

Create and retrieve tasks

Worker · authenticated create/retrieve REST calls
export default {
  async fetch(request, env) {
    if (!env.INTEGRATION_KEY || request.headers.get('x-integration-key') !== env.INTEGRATION_KEY) {
      return new Response('Unauthorized', { status: 401 });
    }
    const url = new URL(request.url);
    const headers = { Authorization: `Bearer ${env.SOLVETHREE_API_KEY}`, 'Content-Type': 'application/json' };
    let response;
    if (request.method === 'POST') {
      const requestId = request.headers.get('x-request-id');
      if (!requestId || !/^[A-Za-z0-9_-]{1,64}$/.test(requestId)) return new Response('Provide X-Request-ID', { status: 400 });
      response = await fetch('https://api.solvethree.com/v1/tasks?waitMs=0', {
        method: 'POST', headers, redirect: 'error', signal: AbortSignal.timeout(20000),
        body: JSON.stringify({ site: env.CAPTCHA_SITE, action: 'login', proxy: env.SOLVETHREE_PROXY, requestId }),
      });
    } else if (request.method === 'GET') {
      const id = url.searchParams.get('id');
      if (!id || !/^[A-Za-z0-9_-]{1,128}$/.test(id)) return new Response('Provide a valid id', { status: 400 });
      response = await fetch(`https://api.solvethree.com/v1/tasks/${encodeURIComponent(id)}?waitMs=15000`, {
        headers, redirect: 'error', signal: AbortSignal.timeout(25000),
      });
    } else return new Response('Use POST or GET', { status: 405 });
    if (!response.ok) return Response.json({ error: 'Solver API request failed', status: response.status }, { status: response.status });
    return Response.json(await response.json(), { headers: { 'Cache-Control': 'no-store' } });
  },
};
// Store keys in Worker secret bindings. The task proxy field does not add a
// generic HTTP proxy option to Workers fetch; another client must submit through it.
  1. 1.

    Create a task

    Send POST to the Worker with x-integration-key and x-request-id. Choose a request ID containing 1–64 ASCII letters, numbers, underscores, or hyphens. The Worker builds the task body from its configured target and proxy; it doesn't use a caller-supplied body.

  2. 2.

    Keep the task ID

    The Worker returns the solver’s JSON response. Save the returned task ID and retain the request ID used to create it.

  3. 3.

    Retrieve the result

    Send GET to the Worker with the task ID in the id query parameter and the same integration-key header. The handler validates the ID’s syntax and retrieves that task from solvethree.

Both upstream requests authenticate with the workspace key. The handler forwards non-success HTTP status codes with a generic error body, and marks successful JSON responses Cache-Control: no-store. It doesn’t check task ownership beyond the shared integration key; add that authorization before exposing retrieval to separate users.

Handle timeouts without creating another task

Creation uses waitMs=0 with a 20-second fetch timeout. Retrieval uses waitMs=15000 with a 25-second fetch timeout. These limits bound individual HTTP calls, not the task’s entire lifetime.

If creation times out before you receive a task ID, repeat the same task request with the same request ID. That recovers the existing task if it was created. Don’t generate a new ID just because the connection failed: the first request may already have reached solvethree. Reusing an ID with changed inputs conflicts.

The example doesn’t catch fetch exceptions, including timeouts. Add exception handling if your caller needs a structured error response, but keep the request ID available for recovery.

  • pending or solving: retrieve the same task again, subject to an overall deadline in your caller.
  • ready: the solution is available; stop polling and pass it to the submission client.
  • failed: stop polling and inspect errorCode and error. Fix the reported problem before creating another task.

An HTTP error and a task with status failed are different failures. The handler checks HTTP status first; the caller must still inspect the task status in a successful response. For diagnosis, see CAPTCHA API errors.

Submit through a client that can use the proxy

The Worker example calls only solvethree. The task’s proxy field tells the solver which proxy to use; it does not change the Worker’s network routing. Workers fetch has no generic proxy URL option.

Send the ready solution to a client that can submit to the intended target through the same proxy. Use the solution’s returned userAgent and secChUa rather than unrelated browser headers. A ready task means solvethree generated a solution; acceptance depends on the subsequent request to the target.

Run your first task.

Start with $5 in workspace credit.

Create a workspace