Call the CAPTCHA API from Spring Boot

By solvethree4 min read

Register one server-side CaptchaClient bean, create a task once, then poll its saved ID. The helper below uses synchronous Spring RestClient calls and returns Gson JsonObject responses.

On this page

Add the dependencies and register the helper

The supplied helper uses Java 21+, Spring Web 7.0.10, and Gson 2.15.0. In an existing Spring Boot project, keep Boot’s managed compatible Spring version rather than overriding only spring-web to match the example. Add Gson as a dependency if your application doesn’t already include it.

Spring · a server-side RestClient service
// Java 21+, Spring Web 7.0.10 and Gson 2.15.0; use your Boot dependency management.
// Save as CaptchaClient.java in your application's package.
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.http.HttpClient;
import java.time.Duration;
import org.springframework.http.MediaType;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

public final class CaptchaClient {
    private final RestClient api;
    public CaptchaClient(String key) {
        var transport = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
            .followRedirects(HttpClient.Redirect.NEVER).build();
        var factory = new JdkClientHttpRequestFactory(transport);
        factory.setReadTimeout(Duration.ofSeconds(25));
        api = RestClient.builder().baseUrl("https://api.solvethree.com/v1")
            .requestFactory(factory).defaultHeader("Authorization", "Bearer " + key).build();
    }
    public JsonObject create(String site, String proxy, String requestId) {
        var input = new JsonObject();
        input.addProperty("site", site);
        input.addProperty("action", "login");
        input.addProperty("proxy", proxy);
        input.addProperty("requestId", requestId); // Save before calling create.
        String body = api.post().uri("/tasks?waitMs=0").contentType(MediaType.APPLICATION_JSON)
            .body(input.toString()).retrieve().body(String.class);
        return JsonParser.parseString(body).getAsJsonObject();
    }
    public JsonObject get(String id) {
        if (!id.matches("[A-Za-z0-9_-]+")) throw new IllegalArgumentException("Invalid task ID");
        String body = api.get().uri("/tasks/{id}?waitMs=15000", id)
            .retrieve().body(String.class);
        return JsonParser.parseString(body).getAsJsonObject();
    }
}
// Construct once with System.getenv("SOLVETHREE_API_KEY") in a server-side @Bean.
// Call create once, save its id, then get(id) until ready/failed or your deadline.
// RestClient throws on HTTP errors; keep the saved requestId and any known task id.

Save the class as CaptchaClient.java in your application’s package. In a server-side @Bean factory method, construct it once using the SOLVETHREE_API_KEY environment variable. Check that the key is configured at startup. The helper adds it as a Bearer authorization header; keep it out of browser code.

Reuse that bean through constructor injection. RestClient is reusable across threads, but each request blocks its calling thread. Don’t invoke these methods on a WebFlux event loop. Spring’s REST client documentation describes the synchronous client.

The transport uses a 10-second connect timeout, a 25-second read timeout, and no redirects. Responses are read as strings and parsed with Gson, not mapped to typed response objects.

Set the site, action, and proxy

Before calling create, configure an active profile for the target site in your workspace. Sending a hostname and action doesn’t create that profile. See site profile setup if the site isn’t configured yet.

  • Pass the hostname as site, not a full page URL. A reCAPTCHA request has no url or type field.
  • The helper hardcodes action to login. Change that value, or make it a method parameter, if the intended request uses a different action.
  • Pass one proxy URL. The API requires exactly one of proxy or proxyCandidates; this helper implements the single-proxy option.
  • The helper omits recaptchaType, so the API uses its enterprise-v3 default.

Create once, then poll the saved task ID

  1. 1.

    Save a request ID before creation

    Choose a requestId containing 1–64 ASCII letters, numbers, underscores, or hyphens. Save it with the task inputs before calling create. It lets you recover the same task if the create response is lost.

  2. 2.

    Call create and save the returned id

    Call the bean’s create method with the hostname, proxy, and saved request ID. It sends one POST to /v1/tasks with waitMs=0. Save the returned id; use that task ID, not the request ID, for subsequent lookups.

  3. 3.

    Call get until the task reaches a terminal status

    Pass the saved task ID to get. Each lookup requests waitMs=15000 and blocks while waiting for the response. Continue for pending or solving, subject to an application-level deadline. Stop for ready or failed. The HTTP timeouts limit individual requests, not the whole polling loop.

  4. 4.

    Read the solution only when ready

    For reCAPTCHA, submit solution.token to the intended target—not the task ID. Use the returned userAgent and secChUa with the same successful proxy. Don’t substitute unrelated browser headers or change the target URL. A ready task supplies a solution; it doesn’t establish that the target accepted your subsequent request.

Handle HTTP errors separately from failed tasks

The create call times out before you receive a task ID.

The server may have created the task even though your client didn’t receive its response.

Try this: Recover by repeating the same task request with the saved requestId. Matching inputs return the existing task. Don’t generate a new request ID unless you intend to create a new logical task.

A get call fails at the HTTP layer.

RestClient.retrieve() throws on non-success HTTP responses. That is not a task status of failed.

Try this: Handle the HTTP error before attempting to read a success payload. Keep the known task ID for recovery rather than creating another task. Diagnose invalid inputs before retrying them unchanged.

The task response has status failed.

Read its errorCode and error; site-unreachable is one possible task error code.

Try this: Stop polling that task and address the reported cause before starting another. Failed solves are refunded; don’t assume the failure means insufficient credit.

Reusing a request ID produces a conflict.

Compare the new task inputs with those saved for that request ID.

Try this: Use the original inputs to recover the existing task. Use a new request ID for intentionally different task inputs.

For error interpretation, see CAPTCHA API errors.

Run your first task.

Start with $5 in workspace credit.

Create a workspace