Use the Go SDK in a net/http server

By solvethree4 min read

Create one shared solvethree client, return the task ID from an authenticated POST, and retrieve its status through a separate GET. Don’t call Solve inside the create handler: this integration lets the caller wait for the result without keeping the original request open.

On this page

Configure the server and target

  1. 1.

    Install the Go SDK

    Use Go 1.22 or newer. In an initialized Go module, install the official SDK with go get solvethree.com/[email protected]. Save the example below as main.go.

  2. 2.

    Set the server environment

    Set SOLVETHREE_API_KEY to your workspace API key and INTEGRATION_KEY to a separate secret for callers of this internal server. Set CAPTCHA_SITE to the target hostname and SOLVETHREE_PROXY to the HTTP or HTTPS proxy URL used for the solve. Keep both keys on trusted servers.

  3. 3.

    Prepare the configured target

    Make sure the site has an active profile available to your workspace; see site profile setup. The example fixes the action to login. Change it to the action your target expects before running the server. Supplying a hostname and action doesn’t create a profile.

Go net/http · create and retrieve tasks
package main

import (
    "context"
    "encoding/json"
    "errors"
    "log"
    "net/http"
    "os"
    "time"
    solvethree "solvethree.com/sdk"
)

func main() {
    client, err := solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"), nil)
    if err != nil { log.Fatal(err) }
    secret := os.Getenv("INTEGRATION_KEY")
    if secret == "" { log.Fatal("Set INTEGRATION_KEY") }
    authorize := func(w http.ResponseWriter, r *http.Request) bool {
        if r.Header.Get("X-Integration-Key") != secret { http.Error(w, "Unauthorized", 401); return false }
        return true
    }
    respond := func(w http.ResponseWriter, result *solvethree.TaskResult, err error) {
        w.Header().Set("Content-Type", "application/json")
        if err != nil {
            var recovery *solvethree.Error
            w.WriteHeader(http.StatusBadGateway)
            ids := map[string]string{"error": "Solver request failed"}
            if errors.As(err, &recovery) { ids["requestId"] = recovery.RequestID; ids["taskId"] = recovery.TaskID }
            _ = json.NewEncoder(w).Encode(ids)
            return
        }
        _ = json.NewEncoder(w).Encode(result)
    }
    mux := http.NewServeMux()
    mux.HandleFunc("POST /captcha/tasks", func(w http.ResponseWriter, r *http.Request) {
        if !authorize(w,r) { return }
        input := solvethree.TaskRequest{Site: os.Getenv("CAPTCHA_SITE"), Action:"login",
            Proxy:os.Getenv("SOLVETHREE_PROXY"), RequestID:r.Header.Get("X-Request-ID")}
        if err := input.Validate(); err != nil { http.Error(w,"Invalid task input",400);return }
        ctx,cancel := context.WithTimeout(r.Context(),20*time.Second);defer cancel()
        zero:=0
        result,err:=client.CreateTask(ctx,input,&zero)
        respond(w,result,err)
    })
    mux.HandleFunc("GET /captcha/tasks/{id}", func(w http.ResponseWriter, r *http.Request) {
        if !authorize(w,r) { return }
        ctx,cancel:=context.WithTimeout(r.Context(),25*time.Second);defer cancel()
        result,err:=client.GetTask(ctx,r.PathValue("id"),15000)
        respond(w,result,err)
    })
    server:=&http.Server{Addr:"127.0.0.1:3000", Handler:mux, ReadHeaderTimeout:5*time.Second}
    log.Fatal(server.ListenAndServe())
}

Run the module with go run .. The server listens on 127.0.0.1:3000. It uses Go’s standard net/http router, so no framework is required. The SDK client is created once and is safe for concurrent use.

The task uses the default enterprise-v3 reCAPTCHA mode. It supplies a site hostname, action, and single proxy—not a type or url. The POST body isn’t used, so callers can’t select arbitrary targets or proxies.

POST once and keep the task ID

Send POST /captcha/tasks with the X-Integration-Key header. Also send a caller-generated X-Request-ID so you can recover the task if the connection ends before you receive its ID. Use 1–64 ASCII letters, numbers, underscores, or hyphens.

The handler validates the configured task, then calls CreateTask with a zero wait value. It requests task creation without waiting for the solve to finish. Its 20-second context limits how long this HTTP request waits; it isn’t a solve timeout.

On success, the handler returns the SDK task result. Save its task ID and use the GET route for subsequent checks. Don’t interpret a successful POST as a usable token.

Both routes require the integration key. This is a trusted internal interface, not per-user authorization: callers holding that key share access through the configured workspace.

GET the existing task until it finishes

Send GET /captcha/tasks/{id} with the same integration-key header. The handler calls only GetTask; it never creates a replacement task. It requests up to 15 seconds of waiting within a 25-second request context.

  • pending or solving: keep the same task ID and retrieve it again.
  • ready: read the solution. This is the only status with a usable result.
  • failed: stop polling and inspect errorCode and error. Correct the cause before deciding whether to create another task.

Check the HTTP response status before decoding a successful task result. In this server, unauthorized requests return 401, invalid task inputs return 400, and SDK request errors return 502. A returned task whose status is failed is a different outcome from an HTTP request failure.

Recover a timeout without creating another solve

A timeout doesn’t prove that creation failed. Canceling the request context stops the local wait, not the remote task. The SDK never automatically retries the create POST.

  1. 1.

    If you have a task ID, retrieve it

    Use the GET route with that ID. On SDK errors, the example’s JSON error response includes taskId and requestId when the SDK exposes them.

  2. 2.

    If you only have the request ID, replay the original creation

    Send the POST again with the same X-Request-ID and unchanged task inputs. That means keeping the configured hostname, action, and proxy unchanged. The same request ID and task request recover the existing task; different inputs with that ID conflict.

  3. 3.

    Keep the ID before the first request

    The SDK generates a request ID when one isn’t supplied, but a disconnected caller may never receive it. Choosing and retaining X-Request-ID before the POST avoids depending on the error response for recovery.

Use the ready result with the same proxy

This server returns the result; the SDK doesn’t submit it to the target. Your submission code must use the token, returned userAgent and secChUa browser identity, and the successful proxy. In this single-proxy example, keep using the configured proxy rather than switching IPs after solving.

A ready task means a solution was generated, not that the target accepted your application request. If submission fails, inspect the outgoing token, identity, proxy, and target request before paying for another solve.

Run your first task.

Start with $5 in workspace credit.

Create a workspace