Solve reCAPTCHA Enterprise with Go
Install solvethree.com/sdk and call Solve with your target hostname, action and proxy. The SDK creates the task once and polls for the result. Your application then submits the token to the target.
On this page
Install and configure the SDK
Use Go 1.22+. In your Go module, run:
go get solvethree.com/sdk@v0.1.0- Set
SOLVETHREE_API_KEYto your workspace API key andSOLVETHREE_PROXYto your HTTP(S) proxy URL. Keep both in server configuration. - Create an active profile for the target hostname in your workspace. See site profile setup.
- Replace
accounts.example.comandloginwith the hostname and action your target uses.Sitetakes a hostname, without a scheme or path.
Solve one task
Save this as main.go and run go run ..
package main
import (
"context"
"fmt"
"log"
"os"
"time"
solvethree "solvethree.com/sdk"
)
func main() {
client, err := solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"), nil)
if err != nil { log.Fatal(err) }
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
input := solvethree.TaskRequest{Site: "accounts.example.com", Action: "login", Proxy: os.Getenv("SOLVETHREE_PROXY")}
result, err := client.Solve(ctx, input, solvethree.SolveOptions{})
if err != nil { log.Fatal(err) }
fmt.Println("Ready task:", result.ID)
// Submit result.Solution.Token with its browser identity and original proxy.
}The context bounds local waiting to 180 seconds. Solve returns a ready TaskResult or an error; you do not need another polling loop around it. This request uses the default Enterprise v3 mode. For regular v3, set RecaptchaType: "v3". Leave Type and URL unset for reCAPTCHA.
Submit the token to your target
Send result.Solution.Token in the application’s CAPTCHA field. The printed result.ID identifies the task; it is not the token.
Use result.Solution.UserAgent as User-Agent and result.Solution.SecChUA as sec-ch-ua. Route the target request through the solve’s successful proxy, selected with solvethree.SuccessfulProxy(input, result).
Include the application’s other form fields, cookies and CSRF value, then check its response. The SDK returns a solution; it does not submit the application request for you.
Keep the IDs if waiting is interrupted
Canceling the context stops local waiting, not remote work. *solvethree.Error exposes RequestID and TaskID. Retrieve a known task with GetTask; if creation is uncertain, replay the exact original inputs with the same RequestID.
For recovery across process restarts, assign and save RequestID with the task inputs before creation. A *TaskFailedError is terminal: inspect its ErrorCode and retrieve the task for its error text. Correct the cause before starting another solve.
Alternative: call REST with the standard library
If you prefer to own the HTTP calls, this version uses net/http directly. Choose this or the SDK example above; running both creates two tasks. The response structs here belong to this standalone example, so their field names differ from the SDK’s types.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
)
type Task struct {
ID string `json:"id"`
Status string `json:"status"`
ErrorCode string `json:"errorCode"`
Error string `json:"error"`
Solution struct {
Token string `json:"token"`
UserAgent string `json:"userAgent"`
SecChUa string `json:"secChUa"`
} `json:"solution"`
}
func main() {
key, proxy := os.Getenv("SOLVETHREE_API_KEY"), os.Getenv("SOLVETHREE_PROXY")
if key == "" || proxy == "" { panic("Set SOLVETHREE_API_KEY and SOLVETHREE_PROXY") }
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
client := &http.Client{Timeout: 30*time.Second}
api := func(method, path string, body []byte) (Task, error) {
var task Task
req, err := http.NewRequestWithContext(ctx, method, "https://api.solvethree.com/v1"+path, bytes.NewReader(body))
if err != nil { return task, err }
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
response, err := client.Do(req)
if err != nil { return task, err }
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 { return task, fmt.Errorf("API HTTP %d", response.StatusCode) }
err = json.NewDecoder(response.Body).Decode(&task)
return task, err
}
requestID := fmt.Sprintf("go-%d", time.Now().UnixNano())
body, err := json.Marshal(map[string]string{
"site":"accounts.example.com", "action":"login", "proxy":proxy, "requestId":requestID,
})
if err != nil { panic(err) }
// Persist requestID and body before creation if this must survive a restart.
task, err := api(http.MethodPost, "/tasks?waitMs=0", body)
if err != nil { panic(fmt.Errorf("request %s: %w", requestID, err)) }
for task.Status == "pending" || task.Status == "solving" {
task, err = api(http.MethodGet, "/tasks/"+url.PathEscape(task.ID)+"?waitMs=15000", nil)
if err != nil { panic(err) }
if task.Status == "pending" || task.Status == "solving" {
select { case <-ctx.Done(): panic(ctx.Err()); case <-time.After(time.Second): }
}
}
if task.Status != "ready" { panic("Task failed: "+task.ErrorCode+": "+task.Error) }
solution := task.Solution
_ = solution // Apply token and returned browser identity using the same proxy.
fmt.Println("Ready task:", task.ID)
}Sources and API reference
Run your first task.
Start with $5 in workspace credit.