Go's concurrency model and fast HTTP performance make it ideal for high-volume CAPTCHA solving. This tutorial shows you how to build a reusable CaptchaAI client in Go.
Requirements
| Requirement | Details |
|---|---|
| Go | 1.21+ |
| Dependencies | Standard library only |
| CaptchaAI API key | Get one here |
CaptchaAI Client
package captchaai
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
type Client struct {
APIKey string
BaseURL string
HTTPClient *http.Client
}
func NewClient(apiKey string) *Client {
return &Client{
APIKey: apiKey,
BaseURL: "https://ocr.captchaai.com",
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// Submit sends a CAPTCHA task and returns the task ID.
func (c *Client) Submit(params map[string]string) (string, error) {
params["key"] = c.APIKey
u, _ := url.Parse(c.BaseURL + "/in.php")
q := u.Query()
for k, v := range params {
q.Set(k, v)
}
u.RawQuery = q.Encode()
resp, err := c.HTTPClient.Get(u.String())
if err != nil {
return "", fmt.Errorf("submit request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
text := string(body)
if !strings.HasPrefix(text, "OK|") {
return "", fmt.Errorf("submit error: %s", text)
}
return strings.SplitN(text, "|", 2)[1], nil
}
// Poll waits for the CAPTCHA result with a timeout.
func (c *Client) Poll(taskID string, timeout time.Duration) (string, error) {
deadline := time.Now().Add(timeout)
params := url.Values{
"key": {c.APIKey},
"action": {"get"},
"id": {taskID},
}
pollURL := c.BaseURL + "/res.php?" + params.Encode()
for time.Now().Before(deadline) {
time.Sleep(5 * time.Second)
resp, err := c.HTTPClient.Get(pollURL)
if err != nil {
continue
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
text := string(body)
if text == "CAPCHA_NOT_READY" {
continue
}
if strings.HasPrefix(text, "OK|") {
return strings.SplitN(text, "|", 2)[1], nil
}
return "", fmt.Errorf("solve error: %s", text)
}
return "", fmt.Errorf("timeout after %v for task %s", timeout, taskID)
}
// Solve submits and polls in one call.
func (c *Client) Solve(params map[string]string) (string, error) {
taskID, err := c.Submit(params)
if err != nil {
return "", err
}
return c.Poll(taskID, 5*time.Minute)
}
// GetBalance returns the account balance in USD.
func (c *Client) GetBalance() (string, error) {
params := url.Values{
"key": {c.APIKey},
"action": {"getbalance"},
}
resp, err := c.HTTPClient.Get(c.BaseURL + "/res.php?" + params.Encode())
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body), nil
}
Solve reCAPTCHA v2
package main
import (
"fmt"
"os"
)
func main() {
client := captchaai.NewClient(os.Getenv("CAPTCHAAI_API_KEY"))
token, err := client.Solve(map[string]string{
"method": "userrecaptcha",
"googlekey": "6Le-wvkS...",
"pageurl": "https://example.com",
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Token: %s\n", token)
}
Solve reCAPTCHA v3
token, err := client.Solve(map[string]string{
"method": "userrecaptcha",
"googlekey": "6Le-wvkS...",
"pageurl": "https://example.com",
"version": "v3",
"action": "login",
})
Solve Cloudflare Turnstile
token, err := client.Solve(map[string]string{
"method": "turnstile",
"sitekey": "0x4AAAAA...",
"pageurl": "https://example.com",
})
Solve Image CAPTCHAs
import "encoding/base64"
imgBytes, _ := os.ReadFile("captcha.png")
imgB64 := base64.StdEncoding.EncodeToString(imgBytes)
text, err := client.Solve(map[string]string{
"method": "base64",
"body": imgB64,
})
fmt.Printf("Recognized: %s\n", text)
Concurrent Solving with Goroutines
Go's goroutines make parallel CAPTCHA solving easy:
package main
import (
"fmt"
"os"
"sync"
)
type Task struct {
SiteKey string
PageURL string
}
func main() {
client := captchaai.NewClient(os.Getenv("CAPTCHAAI_API_KEY"))
tasks := []Task{
{"6Le-wvkS...", "https://example.com/page1"},
{"6Le-wvkS...", "https://example.com/page2"},
{"6Le-wvkS...", "https://example.com/page3"},
}
var wg sync.WaitGroup
results := make([]string, len(tasks))
for i, task := range tasks {
wg.Add(1)
go func(idx int, t Task) {
defer wg.Done()
token, err := client.Solve(map[string]string{
"method": "userrecaptcha",
"googlekey": t.SiteKey,
"pageurl": t.PageURL,
})
if err != nil {
fmt.Fprintf(os.Stderr, "Task %d error: %v\n", idx, err)
return
}
results[idx] = token
}(i, task)
}
wg.Wait()
for i, r := range results {
if r != "" {
fmt.Printf("Task %d: solved (%d chars)\n", i, len(r))
}
}
}
Using with net/http Requests
Inject solved tokens into form submissions:
func submitForm(client *captchaai.Client, targetURL, siteKey string) error {
token, err := client.Solve(map[string]string{
"method": "userrecaptcha",
"googlekey": siteKey,
"pageurl": targetURL,
})
if err != nil {
return err
}
form := url.Values{
"g-recaptcha-response": {token},
"username": {"testuser"},
}
resp, err := http.PostForm(targetURL, form)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Printf("Form response: %d\n", resp.StatusCode)
return nil
}
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
submit request failed: dial tcp: lookup ocr.captchaai.com: no such host |
DNS issue | Check network/DNS settings |
submit error: ERROR_WRONG_USER_KEY |
Bad API key | Verify key from dashboard |
timeout after 5m0s |
Long solve time | Increase timeout duration |
submit error: ERROR_ZERO_BALANCE |
No funds | Top up account balance |
FAQ
Can I use this in a CLI tool?
Yes. The client uses only the standard library. Compile to a single binary with go build.
How many goroutines can safely run in parallel?
CaptchaAI handles high concurrency. 50-100 parallel solves is safe. For higher volumes, use a worker pool with a semaphore channel.
Should I reuse the Client across goroutines?
Yes. The Client struct is safe for concurrent use since http.Client is goroutine-safe.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.