Use the Go SDK in a Gin handler
Use one shared solvethree client and pass a deadline-bound child of c.Request.Context() to Solve. Keep the target configuration and API key on the server, and authenticate the application caller before creating a task.
On this page
Install the dependencies and configure the target
- 1.
Create the Go module and install dependencies
Use Go 1.25 or later for the current Gin release. The solvethree SDK itself requires Go 1.22+. Initialize your application's module with
go mod init, using your own module path. Install Gin following its quickstart, then usego getto addsolvethree.com/[email protected]. The official SDK is served through the domain-hosted public module proxy; it is served from solvethree.com. - 2.
Set the server credentials
Set
SOLVETHREE_API_KEYto your workspace API key andINTEGRATION_KEYto the secret your internal application will send inX-Integration-Key. These are separate credentials: the application caller doesn't need the solvethree API key. - 3.
Configure the fixed target
Set
CAPTCHA_SITEto the target hostname andSOLVETHREE_PROXYto the proxy used for solving. Create an active site profile in the workspace first; see site profile setup. The example fixesActiontologin, so change that value if your integration uses another action. Setting the hostname and action doesn't create a profile.
Add the authenticated Gin route
The client is created once and is safe for concurrent requests. The route checks the internal application credential before calling solvethree. It accepts a request ID from the caller, but not an arbitrary target or proxy.
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
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") }
router:=gin.New()
router.Use(gin.Recovery())
router.POST("/captcha/solve",func(c *gin.Context){
if c.GetHeader("X-Integration-Key")!=secret { c.Status(http.StatusUnauthorized);return }
input:=solvethree.TaskRequest{Site:os.Getenv("CAPTCHA_SITE"),Action:"login",
Proxy:os.Getenv("SOLVETHREE_PROXY"),RequestID:c.GetHeader("X-Request-ID")}
ctx,cancel:=context.WithTimeout(c.Request.Context(),180*time.Second);defer cancel()
result,err:=client.Solve(ctx,input,solvethree.SolveOptions{})
if err!=nil {
var failed *solvethree.TaskFailedError
var recovery *solvethree.Error
if errors.As(err,&failed) { c.JSON(502,gin.H{"taskId":failed.TaskID,"errorCode":failed.ErrorCode});return }
if errors.As(err,&recovery) { c.JSON(502,gin.H{"requestId":recovery.RequestID,"taskId":recovery.TaskID});return }
c.JSON(400,gin.H{"error":"Invalid task input"});return
}
// Return private solution data only to the authorized application caller.
c.JSON(200,result)
})
log.Fatal(router.Run("127.0.0.1:3000"))
}The task supplies Site, Action, and a single Proxy. It leaves Type and URL unset, as required for reCAPTCHA, and uses the default Enterprise v3 mode.
The explicit context deadline bounds the handler's wait to 180 seconds; client disconnection can cancel it sooner. The HTTP server and any reverse proxy must permit that request duration. If they can't, use separate create/retrieve operations rather than keeping this synchronous route open.
The listener binds to loopback. Integrate the route with your application's existing internal authentication and deployment setup; don't expose a paid solve endpoint without caller authentication.
Handle errors without exposing secrets
The handler uses errors.As rather than matching error strings. A terminal TaskFailedError produces a 502 response containing the task ID and error code. Other wrapped SDK errors produce a 502 containing recovery IDs. The fallback returns a fixed 400 message instead of exposing raw errors, credentials, or proxy URLs.
Those 502 responses don't all mean the solve failed. Use errors.Is to distinguish cancellation or deadline expiry. For HTTP-specific handling, errors.As can extract a nested APIError with StatusCode and RetryAfter. Keep that distinction in internal diagnostics rather than treating every error as a reason to create another task.
A terminal failed task should not be polled again. Retrieve it if you need its error text, then correct the cause before starting another solve. Failed solves are refunded.
Keep the IDs when the request times out
Cancellation stops waiting, not remote work. The example returns recovery IDs when it can, but it doesn't persist them. If the caller disconnects, that JSON response may never arrive.
- 1.
Assign the request ID before calling the route
Have the internal caller retain a stable
X-Request-IDfor each logical solve. Valid IDs contain 1–64 ASCII letters, numbers, underscores, or hyphens. The SDK generates an ID when one is missing, but assigning it beforehand makes recovery possible even if the response is lost. - 2.
Retain the exact task inputs
Store the hostname, action, proxy, and request ID in protected application storage. Retrying after an environment configuration change must not silently substitute different task inputs.
- 3.
Recover before creating new work
With a known task ID, call
GetTaskusing a fresh bounded context. Without one, replay the original task throughCreateTaskwith the same request ID and inputs. That returns the existing task; different inputs with the same ID conflict. Continue retrieving only while the task is pending or solving.
Solve creates once and retries only temporary polling responses with HTTP status 429, 502, 503, or 504, honoring Retry-After within the context deadline. CreateTask never retries POST automatically, so a creation timeout still needs recovery rather than a new request ID.
Submit the token with its returned identity
A successful handler response contains private solution data for the authorized application caller. Use Solution.Token, not the task ID, in the target request. Use the returned UserAgent and SecChUA, and obtain the successful proxy with SuccessfulProxy(originalTask, readyResult).
The SDK doesn't submit anything to the target. A ready task means a solution was generated, not that the website accepted it. Check the target's response separately.
Sources and API reference
Run your first task.
Start with $5 in workspace credit.