Go SDK
The Go SDK (github.com/xident-io/go-sdk) is an idiomatic Go client for the Xident age verification API. It uses the functional options pattern for configuration, returns (result, *Response, error) triples following the go-github convention, and accepts context.Context on every method. The client is safe for concurrent use across goroutines.
Secret key required: The Go SDK is a server-side SDK. Use your secret key (sk_live_ / sk_test_) from the dashboard. The key is sent via the X-API-Key header. Never expose it in client-side code.
Installation
go get github.com/xident-io/xident-go Quick Start
package main
import (
"context"
"fmt"
"log"
"os"
xident "github.com/xident-io/go-sdk"
)
func main() {
client := xident.NewClient(os.Getenv("XIDENT_SECRET_KEY"))
// 1. Create a verification session
init, _, err := client.Verification.Init(context.Background(), &xident.InitParams{
CallbackURL: "https://yoursite.com/webhook",
MinAge: 18,
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Redirect user to:", init.VerifyURL)
// 2. After user completes verification, check result server-side
session, _, err := client.Verification.GetResult(context.Background(), init.Token)
if err != nil {
log.Fatal(err)
}
if session.IsVerified() {
fmt.Printf("Verified! Age bracket: %d\n", *session.AgeBracket())
}
} Configuration
The client is configured using functional options passed to NewClient. All options are optional -- defaults are production-ready.
client := xident.NewClient("sk_live_xxx",
xident.WithBaseURL("https://staging-api.xident.io"),
xident.WithTimeout(15 * time.Second),
xident.WithMaxRetries(5),
xident.WithHTTPClient(&http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
},
}),
) | Option | Default | Description |
|---|---|---|
WithBaseURL(url) | https://api.xident.io | API base URL. Override for staging or self-hosted. |
WithTimeout(d) | 30s | HTTP request timeout. Ignored if WithHTTPClient is used. |
WithMaxRetries(n) | 3 | Max retries on 5xx errors. Exponential backoff with jitter. Set to 0 to disable. |
WithHTTPClient(hc) | Default http.Client | Custom *http.Client for full control over transport, TLS, proxies. |
WithUserAgent(ua) | Xident-Go/1.0.0 | Override the User-Agent header. |
Verification
Init (Create Session)
Create a verification session. Returns a short-lived token (10-minute TTL) and a URL to redirect the user to.
result, resp, err := client.Verification.Init(ctx, &xident.InitParams{
// Required
CallbackURL: "https://yoursite.com/webhook",
// Optional
MinAge: 18, // 12, 15, 18, 21, or 25
SuccessURL: "https://yoursite.com/success",
FailedURL: "https://yoursite.com/failed",
UserID: "user_123", // your internal user ID
Theme: "dark", // "light" or "dark"
Locale: "de", // "en", "de", "fr", etc.
Metadata: "order_456", // up to 500 chars, passed to webhook
LivenessDifficulty: "hard", // "easy", "normal", "hard"
Purpose: "age_verification",
})
if err != nil {
log.Fatal(err)
}
// result.Token -> "xit_abc123..." (10-minute TTL)
// result.VerifyURL -> "https://verify.xident.io/v/xit_abc123"
// resp.RequestID -> correlation ID for support tickets InitParams Fields
| Field | Type | Required | Description |
|---|---|---|---|
CallbackURL | string | Yes | Webhook URL for verification result |
MinAge | int | No | Age threshold: 12, 15, 18, 21, or 25 |
SuccessURL | string | No | Redirect URL on success |
FailedURL | string | No | Redirect URL on failure |
UserID | string | No | Your internal user ID (passed through to webhook) |
Theme | string | No | "light" or "dark" |
Locale | string | No | Widget language: "en", "de", "fr", etc. |
Metadata | string | No | Opaque string (up to 500 chars) passed to webhook |
LivenessDifficulty | string | No | "easy", "normal", or "hard" |
Purpose | string | No | Verification purpose (shown to user) |
GetResult (Check Session)
Retrieve the verification result for a token. Call this after the user returns from the verification widget or after receiving a webhook. Never trust URL parameters alone -- always re-verify server-side.
session, resp, err := client.Verification.GetResult(ctx, "xit_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Println("Status:", session.Status) // "completed", "failed", "pending", etc.
fmt.Println("Verified:", session.IsVerified()) // true if completed
fmt.Println("Failed:", session.IsFailed()) // true if failed
fmt.Println("Pending:", session.IsPending()) // true if pending or in_progress
fmt.Println("Terminal:", session.IsTerminal()) // true if no more changes possible
fmt.Println("Age bracket:", session.AgeBracket()) // *int: 12, 15, 18, 21, 25, or nil
fmt.Println("Method:", session.Method()) // "ml_fast", "ocr", "self_declaration" Webhook Verification
Xident sends webhooks to your CallbackURL when verification sessions complete, fail, or expire. The X-Xident-Signature header uses HMAC-SHA256 with the format t=TIMESTAMP,v1=HMAC_HEX (Stripe-style).
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", 400)
return
}
signature := r.Header.Get("X-Xident-Signature")
event, err := client.Webhooks.ConstructEvent(body, signature, webhookSecret)
if err != nil {
log.Printf("Webhook verification failed: %v", err)
http.Error(w, "Invalid signature", 400)
return
}
switch event.Type {
case "session.completed":
log.Printf("Verification completed: %v", event.Data)
// Grant access
case "session.failed":
log.Printf("Verification failed: %v", event.Data)
// Handle failure
}
w.WriteHeader(http.StatusOK)
} // Verify signature only (without parsing)
ok, err := client.Webhooks.VerifySignature(payload, signature, secret)
// Custom tolerance (default: 5 minutes)
event, err := client.Webhooks.ConstructEvent(payload, sig, secret, 10*time.Minute)
// Disable replay protection (NOT recommended in production)
event, err := client.Webhooks.ConstructEvent(payload, sig, secret, 0) WebhookEvent Fields
| Field | Type | Description |
|---|---|---|
Type | string | Event type: "session.completed", "session.failed" |
Data | map[string]any | Event payload (session details) |
ID | string | Unique event identifier (for deduplication) |
Created | int64 | Unix timestamp of event creation |
Error Handling
The SDK returns typed errors that map to HTTP status codes. Use errors.As() to match specific error types.
import "errors"
result, _, err := client.Verification.Init(ctx, params)
if err != nil {
// Match specific error types with errors.As
var authErr *xident.AuthenticationError
var valErr *xident.ValidationError
var notFound *xident.NotFoundError
var rateErr *xident.RateLimitError
var srvErr *xident.ServerError
switch {
case errors.As(err, &authErr):
// HTTP 401/403 -- invalid or missing API key
log.Printf("Auth failed: %s (code: %s, request_id: %s)",
authErr.Message, authErr.Code, authErr.RequestID)
case errors.As(err, &valErr):
// HTTP 400 -- bad request parameters
log.Printf("Validation: %s", valErr.Message)
case errors.As(err, ¬Found):
// HTTP 404 -- token or resource not found
log.Printf("Not found: %s", notFound.Message)
case errors.As(err, &rateErr):
// HTTP 429 -- rate limited
log.Printf("Rate limited, retry after %d seconds", rateErr.RetryAfter)
case errors.As(err, &srvErr):
// HTTP 5xx -- server error (SDK auto-retries these)
log.Printf("Server error: %s", srvErr.Message)
default:
// Network error, context canceled, etc.
log.Printf("Error: %v", err)
}
} Error Hierarchy
| Error Type | HTTP Status | Description |
|---|---|---|
*AuthenticationError | 401, 403 | Invalid or missing API key |
*ValidationError | 400, 4xx | Bad request parameters |
*NotFoundError | 404 | Token or resource not found |
*RateLimitError | 429 | Rate limit exceeded (.RetryAfter seconds) |
*ServerError | 5xx | Server error (auto-retried by SDK) |
All error types embed ErrorResponse which provides .Code, .Message, .RequestID, and .Response (the raw *http.Response).
Framework Examples
Gin
package main
import (
"context"
"io"
"log"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
xident "github.com/xident-io/go-sdk"
)
func main() {
client := xident.NewClient(os.Getenv("XIDENT_SECRET_KEY"),
xident.WithTimeout(15*time.Second),
)
webhookSecret := os.Getenv("XIDENT_WEBHOOK_SECRET")
r := gin.Default()
// Start verification
r.POST("/verify", func(c *gin.Context) {
result, _, err := client.Verification.Init(c.Request.Context(), &xident.InitParams{
CallbackURL: "https://example.com/webhook",
MinAge: 18,
SuccessURL: "https://example.com/success",
FailedURL: "https://example.com/failed",
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"token": result.Token,
"verify_url": result.VerifyURL,
})
})
// Webhook handler
r.POST("/webhook", func(c *gin.Context) {
body, _ := io.ReadAll(c.Request.Body)
event, err := client.Webhooks.ConstructEvent(
body, c.GetHeader("X-Xident-Signature"), webhookSecret,
)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid signature"})
return
}
log.Printf("Event: %s %v", event.Type, event.Data)
c.Status(http.StatusOK)
})
// Check verification result
r.GET("/result/:token", func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
defer cancel()
session, _, err := client.Verification.GetResult(ctx, c.Param("token"))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"verified": session.IsVerified(),
"age_bracket": session.AgeBracket(),
"method": session.Method(),
})
})
r.Run(":8080")
} Echo
package main
import (
"context"
"io"
"log"
"net/http"
"os"
"time"
"github.com/labstack/echo/v4"
xident "github.com/xident-io/go-sdk"
)
func main() {
client := xident.NewClient(os.Getenv("XIDENT_SECRET_KEY"))
webhookSecret := os.Getenv("XIDENT_WEBHOOK_SECRET")
e := echo.New()
e.POST("/verify", func(c echo.Context) error {
result, _, err := client.Verification.Init(c.Request().Context(), &xident.InitParams{
CallbackURL: "https://example.com/webhook",
MinAge: 18,
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, map[string]string{
"token": result.Token,
"verify_url": result.VerifyURL,
})
})
e.POST("/webhook", func(c echo.Context) error {
body, _ := io.ReadAll(c.Request().Body)
event, err := client.Webhooks.ConstructEvent(
body, c.Request().Header.Get("X-Xident-Signature"), webhookSecret,
)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid signature"})
}
log.Printf("Event: %s", event.Type)
return c.NoContent(http.StatusOK)
})
e.GET("/result/:token", func(c echo.Context) error {
ctx, cancel := context.WithTimeout(c.Request().Context(), 10*time.Second)
defer cancel()
session, _, err := client.Verification.GetResult(ctx, c.Param("token"))
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, map[string]any{
"verified": session.IsVerified(),
"age_bracket": session.AgeBracket(),
"method": session.Method(),
})
})
e.Logger.Fatal(e.Start(":8080"))
} Fiber
package main
import (
"context"
"log"
"os"
"time"
"github.com/gofiber/fiber/v2"
xident "github.com/xident-io/go-sdk"
)
func main() {
client := xident.NewClient(os.Getenv("XIDENT_SECRET_KEY"))
webhookSecret := os.Getenv("XIDENT_WEBHOOK_SECRET")
app := fiber.New()
app.Post("/verify", func(c *fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
result, _, err := client.Verification.Init(ctx, &xident.InitParams{
CallbackURL: "https://example.com/webhook",
MinAge: 18,
})
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{
"token": result.Token,
"verify_url": result.VerifyURL,
})
})
app.Post("/webhook", func(c *fiber.Ctx) error {
event, err := client.Webhooks.ConstructEvent(
c.Body(), c.Get("X-Xident-Signature"), webhookSecret,
)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid signature"})
}
log.Printf("Event: %s", event.Type)
return c.SendStatus(fiber.StatusOK)
})
app.Get("/result/:token", func(c *fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
session, _, err := client.Verification.GetResult(ctx, c.Params("token"))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(fiber.Map{
"verified": session.IsVerified(),
"age_bracket": session.AgeBracket(),
"method": session.Method(),
})
})
log.Fatal(app.Listen(":8080"))
} Response Types
SessionResult
| Field | Type | Description |
|---|---|---|
ID | string | Unique session identifier |
Status | SessionStatus | "pending", "in_progress", "completed", "failed", "canceled", "claimed" |
LivenessResult | json.RawMessage | Liveness check outcome |
AgeResult | json.RawMessage | Age verification outcome |
OCRResult | json.RawMessage | Document OCR outcome |
FaceMatchResult | json.RawMessage | Face matching outcome |
CountryCode | *string | ISO 3166-1 alpha-2 country code |
MinAge | *int | Minimum age threshold for this session |
ExternalUserID | *string | Your user ID (from InitParams.UserID) |
RequiredMethods | []string | Verification methods required |
RemainingAttempts | *int | Attempts remaining before failure |
CreatedAt | string | RFC 3339 timestamp |
CompletedAt | *string | RFC 3339 timestamp (nil if in progress) |
ExpiresAt | *string | RFC 3339 timestamp |
Helper Methods
| Method | Returns | Description |
|---|---|---|
IsVerified() | bool | True if status is "completed" |
IsFailed() | bool | True if status is "failed" |
IsPending() | bool | True if "pending" or "in_progress" |
IsTerminal() | bool | True if completed, failed, canceled, or claimed |
AgeBracket() | *int | Verified age bracket (12/15/18/21/25) or nil |
Method() | string | Verification method used or "" |
Environment Variables
| Variable | Description |
|---|---|
XIDENT_SECRET_KEY | Your secret API key (sk_live_ or sk_test_) |
XIDENT_WEBHOOK_SECRET | Webhook signing secret (whsec_) |
Related
- JavaScript SDK -- Client-side browser integration
- Node.js SDK -- Server-side Node.js alternative
- All SDKs -- Overview of all Xident SDKs
- API Reference -- Full REST API documentation