Server-side SDK for validating Captcha tokens.
English | 简体中文
go get github.com/Captcha-La/captchala-gopackage main
import (
"fmt"
"log"
captchala "github.com/Captcha-La/captchala-go"
)
func main() {
// Create client
client := captchala.NewClient("your_app_key", "your_app_secret")
// Validate token
result, err := client.Validate(token)
if err != nil {
log.Printf("Validation error: %v", err)
}
if result.Valid {
fmt.Println("Verification passed")
if result.Offline {
fmt.Println("This was an offline verification")
}
} else {
fmt.Printf("Verification failed: %s\n", result.Error)
}
}Create a client with default 5-second timeout.
Create a client with custom timeout.
Validate and consume a token.
Validate a token. If keepToken is true, the token won't be consumed and can be validated again.
Validate a token and forward the end-user IP. The IP is optional but
recommended — pass the end-user's IP from your inbound request; it is used
for additional risk checks. Safe to omit (then use Validate /
ValidateWithOptions).
type ValidateResult struct {
Valid bool // Whether validation passed
Offline bool // Whether this was offline verification
ClientOnly bool // Whether this is a client-only token
ChallengeID string // Challenge ID
Action string // Business action
UID string // User ID bound via bind_uid (for server-side identity check)
Error string // Error message
Warning string // Warning message
CaptchaArgs CaptchaArgs // Solve-context echo (informational only)
}
// CaptchaArgs is the solve-time context, echoed back on validate
// (Geetest-style). All fields are informational — NEVER gate pass/fail on them.
type CaptchaArgs struct {
Platform string // web / android / ios / flutter / windows / ...
UserIP string // end-user IP recorded at solve time
Referer string // web: solve page URL (empty on native)
Pkg string // native: app package / bundle id (empty on web)
SolvedAt int64 // solve completion time (unix seconds)
RiskScore int64 // 0-100, higher = riskier (reCAPTCHA v3 score style)
}result, _ := client.Validate(token)
if result.Valid {
ip := result.CaptchaArgs.UserIP // solve-time IP (informational)
_ = ip
}If you issued the server_token with bind_uid = "user_42", check the result:
result, err := client.Validate(token)
if err == nil && result.Valid && result.UID != expectedUserID {
// pass_token was issued for a different user — reject
}Mint a one-time sct_ server token with default options (no IP/UID binding,
default TTL/maxUses).
Mint a server token with full control. Hand the returned Token to the
browser SDK as the serverToken prop — single-use, action-scoped,
optionally IP/UID-bound.
issue, err := client.IssueServerTokenWithOptions("login", captchala.IssueOptions{
BindingIP: userIP, // backend rejects if a different IP redeems
TTL: 300, // seconds
MaxUses: 5, // SDK retry budget
BindUID: userID, // pair with ValidateResult.UID on verify
})
if err != nil || !issue.OK {
http.Error(w, issue.Error, http.StatusBadRequest)
return
}
// hand issue.Token to the browsertype IssueResult struct {
OK bool
Token string // sct_<hex>
ExpiresIn int // seconds
IssuedAt int64 // unix seconds
Error string
Message string
}
type IssueOptions struct {
BindingIP string // end-user IP for bind_ip enforcement
TTL int // 0 → server default (300)
MaxUses int // 0 → server default
BindUID string // user ID; verify side compares ValidateResult.UID
}Multi-modal content moderation. input is a slice of ModerationItem —
text and image_url entries can be mixed in one call (OpenAI-compatible).
result, err := client.ModerationCheck([]captchala.ModerationItem{
captchala.TextItem(userComment),
captchala.ImageURLItem(uploadedImageURL),
}, userID)
if result.Flagged && result.HasCategory("violence", "csam") {
// hard block
}Convenience wrapper for plain-text moderation.
result, err := client.ModerationText("user comment here", userID)type ModerationItem struct {
Type string // "text" or "image_url"
Text string // for type="text"
ImageURL map[string]any // for type="image_url" — {"url": "https://..."}
}
// Helpers — usually preferred over building items by hand:
captchala.TextItem("...")
captchala.ImageURLItem("https://...")
type ModerationResult struct {
OK bool
Flagged bool
Categories map[string]bool // category name → tripped
ContentType string // "text" / "image" / "mixed"
Raw map[string]any // full upstream payload
Error string
Message string
}
// Method:
result.HasCategory("violence", "csam") // true if ANY of the names tripped| Prefix | Source | Security Level |
|---|---|---|
pt_ |
Main API | High |
offline_ |
Backup Service | Medium |
client_ |
Client-only | Low (cannot verify server-side) |
package main
import (
"net/http"
"os"
"github.com/gin-gonic/gin"
captchala "github.com/Captcha-La/captchala-go"
)
var captchaClient *captchala.Client
func init() {
captchaClient = captchala.NewClient(
os.Getenv("CAPTCHALA_APP_KEY"),
os.Getenv("CAPTCHALA_APP_SECRET"),
)
}
// CaptchaMiddleware validates captcha tokens
func CaptchaMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.PostForm("captcha_token")
if token == "" {
token = c.GetHeader("X-Captcha-Token")
}
if token == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "missing_captcha_token",
})
return
}
result, _ := captchaClient.Validate(token)
if !result.Valid {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "captcha_failed",
"message": result.Error,
})
return
}
// Pass validation info to handlers
c.Set("captcha_offline", result.Offline)
c.Set("captcha_client_only", result.ClientOnly)
c.Next()
}
}
func main() {
r := gin.Default()
r.POST("/login", CaptchaMiddleware(), func(c *gin.Context) {
// Captcha passed, handle login
c.JSON(http.StatusOK, gin.H{"message": "Login successful"})
})
r.Run(":8080")
}# Run tests
go test -v
# Integration tests (requires real credentials)
CAPTCHALA_APP_KEY=xxx CAPTCHALA_APP_SECRET=xxx go test -v
# Benchmarks
go test -bench=.MIT