Go SDK
Official Klime SDK for Go. Track events, identify users, and associate them with groups.
Installation
go get github.com/klimeapp/klime-goQuick Start
package main
import (
"log"
klime "github.com/klimeapp/klime-go"
)
func main() {
client, err := klime.NewClient(klime.Config{
WriteKey: "your-write-key",
})
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
// Identify a user
client.Identify("user_123", map[string]interface{}{
"email": "user@example.com",
"name": "Stefan",
})
// Track an event
client.Track("Button Clicked", map[string]interface{}{
"button": "signup",
"plan": "pro",
}, klime.WithUserID("user_123"))
// Associate user with a group and set group traits
client.Group("org_456", map[string]interface{}{
"name": "Acme Inc",
"plan": "enterprise",
}, klime.WithGroupUserID("user_123"))
}Installation Prompt
Copy and paste this prompt into Cursor, Copilot, or your favorite AI editor to integrate Klime:
Integrate Klime for customer analytics. Klime tracks user activity to identify which customers are healthy vs at risk of churning.
ANALYTICS MODES (determine which applies):
- Companies & Teams: Your customers are companies with multiple team members (SaaS, enterprise tools)
→ Use Identify() + Group() + Track()
- Individual Customers: Your customers are individuals with private accounts (consumer apps, creator tools)
→ Use Identify() + Track() only (no Group() needed)
KEY CONCEPTS:
- Every Track() call requires either userId OR groupId (no anonymous events)
- Use groupId alone for org-level events (webhooks, cron jobs, system metrics)
- Group() links a user to a company AND sets company traits (only for Companies & Teams mode)
- Order doesn't matter - events before Identify/Group still get attributed correctly
BEST PRACTICES:
- Initialize client ONCE at app startup (singleton pattern)
- Store write key in KLIME_WRITE_KEY environment variable
- Call Shutdown() on process exit to flush remaining events
Install: go get github.com/klimeapp/klime-go
import klime "github.com/klimeapp/klime-go"
client, _ := klime.NewClient(klime.Config{WriteKey: os.Getenv("KLIME_WRITE_KEY")})
defer client.Shutdown()
// Identify users at signup/login:
client.Identify("usr_abc123", map[string]interface{}{"email": "jane@acme.com", "name": "Jane Smith"})
// Track key activities:
client.Track("Report Generated", map[string]interface{}{"report_type": "revenue"}, klime.WithUserID("usr_abc123"))
client.Track("Feature Used", map[string]interface{}{"feature": "export", "format": "csv"}, klime.WithUserID("usr_abc123"))
client.Track("Teammate Invited", map[string]interface{}{"role": "member"}, klime.WithUserID("usr_abc123"))
// If Companies & Teams mode: link user to their company and set company traits
client.Group("org_456", map[string]interface{}{"name": "Acme Inc", "plan": "enterprise"}, klime.WithGroupUserID("usr_abc123"))
INTEGRATION WORKFLOW:
Phase 1: Discover
Explore the codebase to understand:
1. What framework is used? (Gin, Echo, Chi, Fiber, net/http, etc.)
2. Where is user identity available? (e.g., ctx.Value("user"), c.Get("user"), session.UserID, JWT claims)
3. Is this Companies & Teams or Individual Customers?
- Look for: organization, workspace, tenant, team, account models → Companies & Teams (use Group())
- No company/org concept, just individual users → Individual Customers (skip Group())
4. Where do core user actions happen? (HTTP handlers, service methods, controllers)
5. Is there existing analytics? (search: segment, posthog, mixpanel, amplitude, Track)
Match your integration style to the framework's conventions.
Phase 2: Instrument
Add these calls using idiomatic patterns for the framework:
- Initialize client once at startup (main.go or init function, use defer Shutdown())
- Identify() in auth/login success handler
- Group() when user-org association is established (Companies & Teams mode only)
- Track() for key user actions (see below)
WHAT TO TRACK:
Active engagement (primary): feature usage, resource creation, collaboration, completing flows
Session signals (secondary): login/session start, dashboard access - distinguishes "low usage" from "churned"
Do NOT track: every HTTP request, health checks, middleware passthrough, background jobs
Phase 3: Verify
Confirm: client initialized, shutdown handled, Identify/Group/Track calls added
Phase 4: Summarize
Report what you added:
- Files modified and what was added to each
- Events being tracked (list event names and what triggers them)
- How userId is obtained (and groupId if Companies & Teams mode)
- Any assumptions made or questionsAPI Reference
Constructor
client, err := klime.NewClient(klime.Config{
WriteKey: "your-write-key", // Required: Your Klime write key
Endpoint: "https://i.klime.com", // Optional: API endpoint
FlushInterval: 2 * time.Second, // Optional: Interval between flushes
MaxBatchSize: 20, // Optional: Max events per batch (max: 100)
MaxQueueSize: 1000, // Optional: Max queued events
RetryMaxAttempts: 5, // Optional: Max retry attempts
RetryInitialDelay: 1 * time.Second, // Optional: Initial retry delay
FlushOnShutdown: boolPtr(true), // Optional: Auto-flush on shutdown
HTTPClient: &http.Client{}, // Optional: Custom HTTP client
Logger: slog.Default(), // Optional: Structured logger (log/slog)
OnError: func(err error, events []*klime.Event) { ... }, // Optional: Error callback
OnSuccess: func(response *klime.BatchResponse) { ... }, // Optional: Success callback
})Methods
Track(event string, properties map[string]interface{}, opts ...TrackOption)
Track a user event. Use WithUserID() to specify the user.
// Basic tracking
client.Track("Button Clicked", map[string]interface{}{
"button": "signup",
"plan": "pro",
}, klime.WithUserID("user_123"))
// With group context
client.Track("Feature Used", map[string]interface{}{
"feature": "export",
}, klime.WithUserID("user_123"), klime.WithGroupID("org_456"))Identify(userID string, traits map[string]interface{}, opts ...IdentifyOption)
Identify a user with traits.
client.Identify("user_123", map[string]interface{}{
"email": "user@example.com",
"name": "Stefan",
"plan": "pro",
})Group(groupID string, traits map[string]interface{}, opts ...GroupOption)
Associate a user with a group and/or set group traits.
// Associate user with a group and set group traits
client.Group("org_456", map[string]interface{}{
"name": "Acme Inc",
"plan": "enterprise",
}, klime.WithGroupUserID("user_123"))
// Just link a user to a group
client.Group("org_456", nil, klime.WithGroupUserID("user_123"))
// Just update group traits
client.Group("org_456", map[string]interface{}{
"plan": "enterprise",
"employeeCount": 50,
})Flush()
Manually flush queued events immediately.
client.Flush()Shutdown()
Gracefully shutdown the client, flushing remaining events.
client.Shutdown()QueueSize() int
Return the number of events currently in the queue.
pending := client.QueueSize()
fmt.Printf("%d events waiting to be sent\n", pending)Synchronous Methods
For cases where you need confirmation that events were sent (e.g., before process exit, in tests), use the synchronous variants:
TrackSync(ctx, event, properties, opts...) (*BatchResponse, error)
Track an event synchronously. Returns *BatchResponse or error.
ctx := context.Background()
response, err := client.TrackSync(ctx, "Critical Action", map[string]interface{}{
"key": "value",
}, klime.WithUserID("user_123"))
if err != nil {
if sendErr, ok := err.(*klime.SendError); ok {
fmt.Printf("Failed to send: %s\n", sendErr.Message)
}
}
fmt.Printf("Sent! Accepted: %d\n", response.Accepted)IdentifySync(ctx, userID, traits, opts...) (*BatchResponse, error)
Identify a user synchronously. Returns *BatchResponse or error.
GroupSync(ctx, groupID, traits, opts...) (*BatchResponse, error)
Associate a user with a group synchronously. Returns *BatchResponse or error.
Features
- Automatic Batching: Events are automatically batched and sent every 2 seconds or when the batch reaches 20 events
- Automatic Retries: Failed requests are automatically retried with exponential backoff
- Thread-Safe: Safe to use from multiple goroutines
- Process Exit Handling: Automatically flushes events on SIGINT/SIGTERM
- Zero Dependencies: Uses only Go standard library
Performance
When you call Track(), Identify(), or Group(), the SDK:
- Adds the event to an in-memory queue protected by a mutex (microseconds)
- Returns immediately without waiting for network I/O
Events are sent to Klime's servers in background goroutines. This design means:
- No network blocking: HTTP requests happen asynchronously in background goroutines
- No latency impact: Tracking calls add < 1ms to your request handling time
- Automatic batching: Events are queued and sent in batches (default: every 2 seconds or 20 events)
// This returns immediately - no HTTP request is made here
client.Track("Button Clicked", map[string]interface{}{
"button": "signup",
}, klime.WithUserID("user_123"))
// Your code continues without waiting
json.NewEncoder(w).Encode(map[string]bool{"success": true})The only blocking operation is Flush(), which waits for all queued events to be sent. This is typically only called during graceful shutdown.
Configuration
Default Values
| Setting | Default |
|---|---|
FlushInterval | 2 seconds |
MaxBatchSize | 20 events |
MaxQueueSize | 1000 events |
RetryMaxAttempts | 5 attempts |
RetryInitialDelay | 1 second |
FlushOnShutdown | true |
Logging
The SDK supports Go's built-in log/slog (Go 1.21+). Pass a logger to enable structured logging:
import "log/slog"
client, _ := klime.NewClient(klime.Config{
WriteKey: "your-write-key",
Logger: slog.Default(), // Use default logger
})
// Or with a custom logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
client, _ := klime.NewClient(klime.Config{
WriteKey: "your-write-key",
Logger: logger,
})If Logger is nil (default), no logging is performed.
Callbacks
client, _ := klime.NewClient(klime.Config{
WriteKey: "your-write-key",
OnError: func(err error, events []*klime.Event) {
// Report to your error tracking service
sentry.CaptureException(err)
log.Printf("Failed to send %d events: %v", len(events), err)
},
OnSuccess: func(response *klime.BatchResponse) {
log.Printf("Sent %d events", response.Accepted)
},
})Error Handling
The SDK automatically handles:
- Transient errors (429, 503, network failures): Retries with exponential backoff
- Permanent errors (400, 401): Logs error and drops batch
- Rate limiting: Respects
Retry-Afterheader
For synchronous operations, use *Sync() methods which return *SendError on failure:
response, err := client.TrackSync(ctx, "Event", nil, klime.WithUserID("user_123"))
if err != nil {
if sendErr, ok := err.(*klime.SendError); ok {
fmt.Printf("Failed: %s, events: %d\n", sendErr.Message, len(sendErr.Events))
}
}