Go

Go SDK

Official Klime SDK for Go. Track events, identify users, and associate them with groups.

Installation

go get github.com/klimeapp/klime-go

Quick 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 questions

API 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:

  1. Adds the event to an in-memory queue protected by a mutex (microseconds)
  2. 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

SettingDefault
FlushInterval2 seconds
MaxBatchSize20 events
MaxQueueSize1000 events
RetryMaxAttempts5 attempts
RetryInitialDelay1 second
FlushOnShutdowntrue

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-After header

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))
    }
}