Go SDK
More
The official ZeroCaptcha client for Go: create a Cloudflare Turnstile task or a Cloudflare challenge page’s task, wait for its result, read your balance, and check a task callback’s signature. It uses the standard library only, and needs Go 1.22 or later.
Every task is real and paid from your prepaid balance, and only a task that succeeds is charged.
Install
Section titled “Install”go get github.com/zerocaptcha/zerocaptcha-goThe module is not published yet. Until it is, call the API with net/http, as the
quickstart’s Go program does: it makes the same calls.
Give the client your API key (zc_live_…, from the dashboard’s API keys page) and the API’s
address. Keep both in your environment rather than in your code.
package main
import ( "context" "errors" "fmt" "log" "os" "time"
zerocaptcha "github.com/zerocaptcha/zerocaptcha-go")
func main() { client, err := zerocaptcha.NewClient(os.Getenv("ZEROCAPTCHA_KEY"), os.Getenv("ZEROCAPTCHA_API")) if err != nil { log.Fatal(err) } ctx := context.Background()
// Create a task and wait for its token: one call. token, err := client.Solve(ctx, zerocaptcha.NewTask{ WebsiteURL: "https://shop.example.com/login", // the page with the widget WebsiteKey: "0x4AAAAAAAB1cD2eF3gH4iJ5", // its data-sitekey // The widget's data-action and data-cdata, or the action and cData options of // turnstile.render(). Leave out any the widget does not set. Action: "login", CData: "session-7f3a9c2e", // Proxy: "http://user:pass@proxy.example.net:8080", // to solve through your own proxy // CallbackURL: "https://hooks.example.com/zerocaptcha", // to be called when it ends }) var failed *zerocaptcha.TaskFailedError switch { case errors.As(err, &failed): fmt.Println(failed.Code) // such as ERROR_CAPTCHA_UNSOLVABLE; nothing was charged case err != nil: log.Fatal(err) default: fmt.Println(token) }
// Or step by step. task, err := client.CreateTask(ctx, zerocaptcha.NewTask{ WebsiteURL: "https://shop.example.com/login", WebsiteKey: "0x4AAAAAAAB1cD2eF3gH4iJ5", Action: "login", // the widget's data-action, if it sets one CData: "session-7f3a9c2e", // the widget's data-cdata, if it sets one }, zerocaptcha.CreateOptions{ // Your ID for this task, sent as the Idempotency-Key; one is made for you when you give none. IdempotencyKey: "login-2026-10-01-0001", }) if err != nil { log.Fatal(err) } done, err := client.WaitForResult(ctx, task.ID, zerocaptcha.WaitOptions{Timeout: 2 * time.Minute}) if err != nil { log.Fatal(err) } fmt.Println(done.Solution.Token, done.Cost)
// Your balance, in US dollars. balance, err := client.GetBalance(ctx) if err != nil { log.Fatal(err) } fmt.Println(balance.Available)}| Function | What it does |
|---|---|
NewClient(apiKey, baseURL, options...) |
A client; WithHTTPClient swaps in your own *http.Client, such as one with a proxy. |
CreateTask(ctx, NewTask{...}, CreateOptions{...}) |
Creates a task: WebsiteURL, WebsiteKey, and optionally Type, Action, CData, Proxy, CallbackURL. |
GetTask(ctx, id) |
Reads a task; its token is in Solution while it is available. |
WaitForResult(ctx, id, WaitOptions{...}) |
Polls every 2 seconds, for up to 3 minutes by default, until the task ends. |
Solve(ctx, NewTask{...}) |
CreateTask then WaitForResult: the token. |
CreateChallengeTask(ctx, NewChallengeTask{...}) |
Creates a Cloudflare challenge page’s task, through your proxy: WebsiteURL, Proxy, optionally CallbackURL. |
SolveChallenge(ctx, NewChallengeTask{...}) |
CreateChallengeTask then WaitForResult: a *Clearance with CfClearance, UserAgent and TokenExpiresAt. |
GetBalance(ctx) |
Available, Held and Currency, as decimal strings. |
VerifySignature(secret, header, body, tolerance, now) |
Whether a callback is genuine; zero values mean five minutes and now. |
Proxy: "http://user:pass@proxy.example.net:8080"solves a task through your proxy.CreateTasksends anIdempotency-Keywith every call, one of its own unless you give one inCreateOptions, so retrying it never makes a second task.- A request the API asks you to slow down (429) or cannot serve for a moment (502, 503, 504) is
tried again after the wait it asks for, three times in all, as is one that got no answer or an
answer cut short, with the same
Idempotency-Key. Any other refusal is an*APIErrorwith the API’sCode, such asinsufficient_funds, and itsRequestID. WaitForResultnever runs past itsTimeout: each read gets only the time left, and a retry that would wait longer than that is not made. A task that fails or expires is a*TaskFailedError; a wait that runs out is a*WaitTimeoutError, with the task as last read (Task, nil if no read finished in time), and you can wait again. Each call stops when its context does.
Cloudflare challenge pages
Section titled “Cloudflare challenge pages”A challenge page is passed through your proxy, and gives the cf_clearance
cookie with the user agent it is bound to. Send both, through the same proxy:
clearance, err := client.SolveChallenge(ctx, zerocaptcha.NewChallengeTask{ WebsiteURL: "https://shop.example.com/", Proxy: os.Getenv("PROXY_URL"), // such as http://user:pass@proxy.example.net:8080})Callbacks
Section titled “Callbacks”A task created with CallbackURL is POSTed to it once it ends, with the task as JSON. Check each
call’s signature against the raw body, before you parse it:
func callback(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) secret := os.Getenv("ZEROCAPTCHA_CALLBACK_SECRET") if !zerocaptcha.VerifySignature(secret, r.Header.Get(zerocaptcha.SignatureHeader), body, 0, time.Time{}) { http.Error(w, "bad signature", http.StatusUnauthorized) return } w.WriteHeader(http.StatusNoContent)}A call older than five minutes does not verify, so a recorded call cannot be replayed. See Polling and callbacks.