Use the Go SDK in a net/http server
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.
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 asmain.go. - 2.
Set the server environment
Set
SOLVETHREE_API_KEYto your workspace API key andINTEGRATION_KEYto a separate secret for callers of this internal server. SetCAPTCHA_SITEto the target hostname andSOLVETHREE_PROXYto the HTTP or HTTPS proxy URL used for the solve. Keep both keys on trusted servers. - 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.
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.
pendingorsolving: 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 inspecterrorCodeanderror. 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.
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
taskIdandrequestIdwhen the SDK exposes them. - 2.
If you only have the request ID, replay the original creation
Send the POST again with the same
X-Request-IDand 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.
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-IDbefore 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.
Sources and API reference
Run your first task.
Start with $5 in workspace credit.