Tutorials

Solve CAPTCHAs with Go Using CaptchaAI

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.

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.