Title: Go SDK Locale: en URL: https://sensorswave.com/en/docs/data-integration/server-sdks/go/ Description: Go SDK integration guide and API reference The Sensors Wave Go SDK is a lightweight Go event tracking and A/B testing tool that supports user event tracking, user profile management, and full A/B testing capabilities. ## Core Features The Go SDK provides the following core capabilities: - **Event Tracking**: Track user events and custom properties - **User Profile Management**: Full User Property operations including set, increment, append, and more - **A/B Testing Integration**: Support for Feature Gates, experiment variables, and dynamic configuration - **Automatic Impression Logging**: Automatically records A/B testing impression events - **Fast Startup**: Supports A/B metadata caching for faster startup ## Installation Install the SDK using `go get`: ```bash go get github.com/sensorswave/sdk-go ``` ## Quick Start ### Basic Event Tracking ```go package main "log" "github.com/sensorswave/sdk-go" ) func main() { // Create client (minimal configuration) client, err := sensorswave.New( sensorswave.Endpoint("https://your-endpoint.com"), sensorswave.SourceToken("your-source-token"), ) if err != nil { log.Fatal(err) } defer client.Close() // Track an event user := sensorswave.User{ LoginID: "user-123", AnonID: "device-456", } client.TrackEvent(user, "PageView", sensorswave.Properties{ "page": "/home", }) } ``` ### Enable A/B Testing To enable A/B testing, provide an `ABConfig`: ```go cfg := sensorswave.Config{ AB: &sensorswave.ABConfig{ ProjectSecret: "your-project-secret", }, } client, err := sensorswave.NewWithConfig( sensorswave.Endpoint("https://your-endpoint.com"), sensorswave.SourceToken("your-source-token"), cfg, ) // Now you can use A/B testing methods result, _ := client.GetExperiment(user, "my_experiment") // Get parameters from the experiment result btnColor := result.GetString("button_color", "blue") showBanner := result.GetBool("show_banner", false) discount := int(result.GetNumber("discount_percent", 0)) fmt.Printf("Experiment variant: %s, button color: %s, show banner: %v, discount: %d%%\n", result.Key, btnColor, showBanner, discount) ``` ## API Reference ### Client Interface The SDK provides a `Client` interface with methods organized by category: ```go type Client interface { // ========== Lifecycle Management ========== // Close gracefully shuts down the client, flushing all pending events // Always call this before application exit Close() error // ========== User Identity ========== // Identify associates an Anonymous ID with a Login ID (signup event) // Creates a $Identify event linking the user's anonymous session with their authenticated identity Identify(user User) error // ========== Event Tracking ========== // TrackEvent tracks a custom event with properties // This is the primary method for tracking user behaviors TrackEvent(user User, event string, properties Properties) error // Track directly submits a fully-populated Event struct // For advanced scenarios; use TrackEvent for normal usage Track(event Event) error // ========== User Profile Operations ========== // ProfileSet sets user profile properties ($set) // Overwrites existing values ProfileSet(user User, properties Properties) error // ProfileSetOnce sets properties only if they don't exist ($set_once) // Used for recording first-time values like registration date ProfileSetOnce(user User, properties Properties) error // ProfileIncrement increments numeric user profile properties ($increment) // Used for counters like login count, points ProfileIncrement(user User, properties Properties) error // ProfileAppend appends values to list user profile properties ($append) // Allows duplicates in the list ProfileAppend(user User, properties ListProperties) error // ProfileUnion adds unique values to list user profile properties ($union) // Ensures no duplicates in the list ProfileUnion(user User, properties ListProperties) error // ProfileUnset removes user profile properties ($unset) // Deletes specified properties from the user profile ProfileUnset(user User, propertyKeys ...string) error // ProfileDelete deletes the entire user profile ($delete) // This operation is irreversible — use with caution ProfileDelete(user User) error // ========== A/B Testing ========== // CheckFeatureGate evaluates a Feature Gate and returns whether it passes // Returns (false, nil) if the key doesn't exist or is not a gate type CheckFeatureGate(user User, key string) (bool, error) // GetFeatureConfig evaluates a feature configuration for a user // Returns an empty result if the key doesn't exist or is not a config type GetFeatureConfig(user User, key string) (ABResult, error) // GetExperiment evaluates an experiment for a user // Returns an empty result if the key doesn't exist or is not an experiment type GetExperiment(user User, key string) (ABResult, error) // GetABSpecs exports the current A/B testing metadata as JSON // Used for caching A/B configuration for fast startup in future sessions // Pass the returned bytes to ABConfig.LoadABSpecs for next initialization GetABSpecs() ([]byte, error) } ``` --- ## User Type > [!WARNING] > > ### User Identity Requirements (Must Read) > > **For all methods except `Identify`:** > > - `AnonID` or `LoginID` must have at least one non-empty value > - **If both are provided, `LoginID` takes priority for user identification** > > **For the `Identify` method only:** > > - **Both `AnonID` and `LoginID` must be non-empty** > - This creates a `$identify` event linking anonymous and authenticated identities ### User Type Definition The `User` type represents user identity for event tracking and A/B testing: ```go type User struct { AnonID string // Anonymous or device ID LoginID string // Login user ID ABUserProperties map[string]interface{} // Properties for A/B testing targeting } ``` ### Usage Examples **Creating users with different ID combinations:** ```go // Valid: LoginID only (for logged-in users) user := sensorswave.User{LoginID: "user-123"} // Valid: AnonID only (for anonymous users) user := sensorswave.User{AnonID: "device-456"} // Valid: Both (LoginID takes priority for identification) user := sensorswave.User{ LoginID: "user-123", AnonID: "device-456", } // Invalid: Neither provided — this will fail user := sensorswave.User{} ``` **For the Identify method — both IDs are required:** ```go // Correct: Both IDs provided err := client.Identify(sensorswave.User{ AnonID: "device-456", // Required LoginID: "user-123", // Required }) // Invalid: Only one ID — Identify will fail err := client.Identify(sensorswave.User{ LoginID: "user-123", // Missing AnonID }) ``` **Adding A/B testing targeting properties:** ```go // Create user user := sensorswave.User{ LoginID: "user-123", AnonID: "device-456", } // Add A/B targeting properties (immutable pattern) user = user.WithABUserProperty(sensorswave.PspAppVer, "11.0") user = user.WithABUserProperty("is_premium", true) // Or add multiple properties at once user = user.WithABUserProperties(sensorswave.Properties{ sensorswave.PspAppVer: "11.0", "is_premium": true, }) ``` --- ## Event Tracking ### Identify - Associate User Identity Associates an Anonymous ID with a Login ID (signup event). ```go user := sensorswave.User{ AnonID: "anon-123", LoginID: "user-456", } if err := client.Identify(user); err != nil { fmt.Printf("Association failed: %v\n", err) return } ``` > **Important**: If the `LoginID` contains sensitive information (such as phone numbers, email addresses, ID numbers, etc.), please encrypt it before transmission to avoid sending sensitive data in plain text. ### TrackEvent - Track Custom Events ```go user := sensorswave.User{ AnonID: "anon-123", LoginID: "user-456", } err := client.TrackEvent(user, "Purchase", sensorswave.Properties{ "product_id": "SKU-001", "price": 99.99, "quantity": 2, }) if err != nil { fmt.Printf("Failed to track event: %v\n", err) return } ``` ### Track - Track Full Event Structure ```go event := sensorswave.NewEvent("anon-123", "user-456", "PageView"). WithProperties(sensorswave.NewProperties(). Set("page", "/home"). Set("referrer", "google.com")) if err := client.Track(event); err != nil { fmt.Printf("Track failed: %v\n", err) return } ``` --- ## User Profile Management User profiles describe user characteristics such as membership level, registration time, preferences, etc. Unlike Event Properties, user profiles are persistently stored and associated with the user. ### ProfileSet - Set Profile Properties Set user profile properties, overwriting if they already exist. ```go user := sensorswave.User{AnonID: "anon-123", LoginID: "user-456"} err := client.ProfileSet(user, sensorswave.Properties{ "name": "John", "level": 5, }) if err != nil { fmt.Printf("ProfileSet failed: %v\n", err) return } ``` ### ProfileSetOnce - Set Only Once Set user profile properties only if they don't already exist. ```go err := client.ProfileSetOnce(user, sensorswave.Properties{ "first_login_date": "2026-01-20", }) if err != nil { fmt.Printf("ProfileSetOnce failed: %v\n", err) return } ``` ### ProfileIncrement - Increment Numeric Properties ```go err := client.ProfileIncrement(user, sensorswave.Properties{ "login_count": 1, "points": 100, }) if err != nil { fmt.Printf("ProfileIncrement failed: %v\n", err) return } ``` ### ProfileAppend - Append to List Properties ```go err := client.ProfileAppend(user, sensorswave.ListProperties{ "tags": []any{"premium"}, }) if err != nil { fmt.Printf("ProfileAppend failed: %v\n", err) return } ``` ### ProfileUnion - Union List Properties ```go err := client.ProfileUnion(user, sensorswave.ListProperties{ "categories": []any{"sports"}, }) if err != nil { fmt.Printf("ProfileUnion failed: %v\n", err) return } ``` ### ProfileUnset - Delete Properties ```go err := client.ProfileUnset(user, "temp_field", "old_field") if err != nil { fmt.Printf("ProfileUnset failed: %v\n", err) return } ``` ### ProfileDelete - Delete User Profile ```go err := client.ProfileDelete(user) if err != nil { fmt.Printf("ProfileDelete failed: %v\n", err) return } ``` > **Note**: `ProfileDelete` deletes all user profile data. Use with caution. --- ## A/B Testing The Go SDK has built-in full A/B testing support for evaluating Feature Gates, experiment variables, and dynamic configurations. ### CheckFeatureGate - Check Feature Gate Check whether a Feature Gate (Boolean Toggle) is enabled. ```go enabled, err := client.CheckFeatureGate(user, "new_feature_gate") if err != nil { fmt.Printf("Evaluation failed: %v\n", err) return } if enabled { // Feature is enabled enableNewFeature() } else { // Feature is not enabled useOldBehavior() } ``` ### GetFeatureConfig - Get Feature Configuration Get dynamic configuration values from a feature configuration. ```go result, err := client.GetFeatureConfig(user, "button_color_config") if err != nil { fmt.Printf("Feature config evaluation failed: %v\n", err) return } // Get string value (with default) color := result.GetString("color", "blue") // Get numeric value (with default) size := result.GetNumber("size", 14.0) // Get boolean value (with default) enabled := result.GetBool("enabled", false) // Get array (with default) items := result.GetSlice("items", []interface{}{}) // Get object (with default) settings := result.GetMap("settings", map[string]interface{}{}) ``` ### GetExperiment - Evaluate Experiment Evaluate an experiment and get experiment variant parameters. ```go result, err := client.GetExperiment(user, "pricing_experiment") if err != nil { fmt.Printf("Experiment evaluation failed: %v\n", err) return } // Get experiment variant parameters pricingStrategy := result.GetString("strategy", "original") // Execute different logic based on experiment variant switch pricingStrategy { case "original": showOriginalPricing() case "discount": discount := int(result.GetNumber("discount_percent", 0)) showDiscountPricing(discount) case "bundle": bundleSize := result.GetNumber("bundle_size", 1.0) showBundlePricing(int(bundleSize)) default: showOriginalPricing() } ``` ### Complete A/B Testing Example ```go package main "fmt" "log" "github.com/sensorswave/sdk-go" ) func main() { // Create a client with A/B testing enabled cfg := sensorswave.Config{ AB: &sensorswave.ABConfig{ ProjectSecret: "your-project-secret", }, } client, err := sensorswave.NewWithConfig( sensorswave.Endpoint("https://your-endpoint.com"), sensorswave.SourceToken("your-source-token"), cfg, ) if err != nil { log.Fatal(err) } defer client.Close() // Create user and add targeting properties user := sensorswave.User{ LoginID: "user-123", AnonID: "device-456", } user = user.WithABUserProperties(sensorswave.Properties{ sensorswave.PspAppVer: "11.0", "is_premium": true, }) // Evaluate Feature Gate if enabled, _ := client.CheckFeatureGate(user, "new_ui"); enabled { fmt.Println("New UI is enabled") } // Evaluate experiment result, _ := client.GetExperiment(user, "pricing_test") variant := result.GetString("variant", "control") fmt.Printf("Experiment variant: %s\n", variant) // Get experiment parameters discount := int(result.GetNumber("discount", 0)) fmt.Printf("Discount: %d%%\n", discount) } ``` --- ## Configuration Options ### Client Configuration | Field | Description | Default | |------|------|--------| | `TrackURIPath` | Event reporting API path | `/in/track` | | `Transport` | Custom HTTP Transport | Default Transport | | `Logger` | Custom Logger implementation | Console output | | `FlushInterval` | Event flush interval | 10 seconds | | `HTTPConcurrency` | Maximum concurrent HTTP requests | 1 | | `HTTPTimeout` | HTTP request timeout | 3 seconds | | `HTTPRetry` | HTTP request retry count | 2 | | `AB` | A/B testing configuration | nil (not enabled) | ### A/B Testing Configuration | Field | Description | Default | |------|------|--------| | `ProjectSecret` | A/B Project Secret (required) | Empty | | `MetaEndpoint` | A/B metadata server address | Empty (uses main Endpoint) | | `MetaURIPath` | A/B metadata API path | `/ab/all4eval` | | `MetaLoadInterval` | Metadata polling interval | 30 seconds (minimum 30 seconds) | | `LoadABSpecs` | Specs cached from `GetABSpecs()` for fast startup | nil | | `StickyHandler` | Custom traffic stickiness handler | nil | | `MetaLoader` | Custom metadata loader | nil | --- ## Advanced Usage: Caching A/B Specs To improve startup performance, you can cache A/B specs and load them during client initialization. ```go // 1. Get specs from an initialized client specs, err := client.GetABSpecs() if err != nil { // Handle error } // 2. Save specs to persistent storage (e.g., file, database, Redis) // saveToStorage(specs) // 3. Load specs when creating a new client savedSpecs := loadFromStorage() cfg := sensorswave.Config{ AB: &sensorswave.ABConfig{ ProjectSecret: "your-project-secret", LoadABSpecs: savedSpecs, // Inject cached specs }, } // The client will be immediately ready for A/B evaluation using cached specs client, err := sensorswave.NewWithConfig(..., cfg) ``` --- ## Predefined Properties The SDK provides predefined property constants for event tracking and user properties: ```go const ( // Device and system properties PspAppVer = "$app_version" // App version PspBrowser = "$browser" // Browser name PspBrowserVer = "$browser_version" // Browser version PspModel = "$model" // Device model PspIP = "$ip" // IP address PspOS = "$os" // Operating system: ios/android/harmony PspOSVer = "$os_version" // OS version // Geolocation properties PspCountry = "$country" // Country PspProvince = "$province" // Province/State PspCity = "$city" // City ) ``` Using in events: ```go err := client.TrackEvent(user, "purchase", sensorswave.Properties{ sensorswave.PspAppVer: "2.1.0", sensorswave.PspCountry: "US", "product_id": "SKU-001", }) ``` Using in A/B testing: ```go user = user.WithABUserProperty(sensorswave.PspAppVer, "2.1.0") user = user.WithABUserProperty(sensorswave.PspCountry, "US") ``` --- ## Complete API Method Reference ### Lifecycle Management | Method | Signature | Description | Example | |------|------|------|------| | **Close** | `Close() error` | Gracefully shuts down the client and flushes pending events. Always call before application exit. | `defer client.Close()` | ### User Identity | Method | Signature | Parameters | Returns | Description | |---|---|---|---|---| | **Identify** | `Identify(user User) error` | `user`: User with both AnonID and LoginID | `error` | Creates a `$Identify` event linking anonymous and authenticated identities | ### Event Tracking | Method | Signature | Parameters | Returns | Description | |---|---|---|---|---| | **TrackEvent** | `TrackEvent(user User, event string, properties Properties) error` | `user`: User identity`event`: Event name`properties`: Event properties | `error` | Primary method for tracking user behaviors with custom properties | | **Track** | `Track(event Event) error` | `event`: Fully-populated event struct | `error` | Low-level API for advanced scenarios. Use TrackEvent for normal usage | ### User Profile Operations | Method | Signature | Description | Use Case | |---|---|---|---| | **ProfileSet** | `ProfileSet(user User, properties Properties) error` | Set or overwrite profile properties | Update username, email, settings | | **ProfileSetOnce** | `ProfileSetOnce(user User, properties Properties) error` | Set properties only if they don't exist | Record registration date, first source | | **ProfileIncrement** | `ProfileIncrement(user User, properties Properties) error` | Increment numeric profile properties | Login count, points, scores | | **ProfileAppend** | `ProfileAppend(user User, properties ListProperties) error` | Append to list profile properties (allows duplicates) | Add purchase history, activity logs | | **ProfileUnion** | `ProfileUnion(user User, properties ListProperties) error` | Add unique values to list profile properties | Add interests, tags, categories | | **ProfileUnset** | `ProfileUnset(user User, propertyKeys ...string) error` | Remove specified profile properties | Clear temporary or deprecated fields | | **ProfileDelete** | `ProfileDelete(user User) error` | Delete entire user profile (irreversible) | GDPR data deletion requests | ### A/B Testing | Method | Signature | Parameters | Returns | Description | |---|---|---|---|---| | **CheckFeatureGate** | `CheckFeatureGate(user User, key string) (bool, error)` | `user`: User, `key`: Gate key | `bool, error` | Evaluate Feature Gate. Returns (false, nil) if key doesn't exist or wrong type | | **GetFeatureConfig** | `GetFeatureConfig(user User, key string) (ABResult, error)` | `user`: User, `key`: Config key | `ABResult, error` | Evaluate feature config. Returns empty result if key doesn't exist or wrong type | | **GetExperiment** | `GetExperiment(user User, key string) (ABResult, error)` | `user`: User, `key`: Experiment key | `ABResult, error` | Evaluate experiment. Returns empty result if key doesn't exist or wrong type | | **GetABSpecs** | `GetABSpecs() ([]byte, error)` | None | `[]byte, error` | Export current A/B metadata as JSON for caching and fast startup | --- ## Best Practices ### 1. Error Handling In production, it is recommended to handle errors for all SDK calls: ```go func trackUserEvent(userID, eventName string, props Properties) { if err := client.TrackEvent(user, eventName, props); err != nil { // Log the error without affecting the main business flow log.Printf("Failed to track event: %v", err) // Optional: send failed events to backup storage // saveToBackupQueue(userID, eventName, props) } } ``` ### 2. Resource Management Use `defer` to ensure the client is properly closed: ```go func main() { client, err := sensorswave.New(...) if err != nil { log.Fatal(err) } defer client.Close() // Ensure client is closed on program exit // Use the client... } ``` ### 3. A/B Testing User Context Build a complete user context for accurate experiment allocation: ```go func buildABUser(userID string) sensorswave.User { user := sensorswave.User{ LoginID: userID, } // Add A/B testing targeting properties user = user.WithABUserProperties(sensorswave.Properties{ sensorswave.PspAppVer: getAppVersion(), sensorswave.PspOS: "linux", "is_premium": checkIsPremium(userID), "registration_date": getUserRegistrationDate(userID), }) return user } ``` ### 3. Event Name Conventions Use **PascalCase** consistently for event names: ```go // Recommended: PascalCase client.TrackEvent(user, "PageView", props) client.TrackEvent(user, "Purchase", props) client.TrackEvent(user, "AddToCart", props) client.TrackEvent(user, "UserRegistered", props) client.TrackEvent(user, "ButtonClicked", props) // Avoid: snake_case (unless there is historical data) client.TrackEvent(user, "page_view", props) client.TrackEvent(user, "purchase", props) client.TrackEvent(user, "add_to_cart", props) ``` **Naming convention notes**: - **Recommended**: Use PascalCase (e.g., `PageView`, `AddToCart`, `UserRegistered`) - **Historical compatibility**: If existing tracking data uses snake_case, you may continue with that style, but **maintain consistency** - **Key point**: Regardless of which style you choose, **ensure all event names within the same project follow a consistent naming convention** ### 4. Event Property Naming Follow consistent naming conventions using `snake_case`: ```go // Recommended practice sensorswave.Properties{ "product_id": "SKU-001", "order_amount": 99.99, "user_tier": "premium", } // Avoid sensorswave.Properties{ "ProductID": "SKU-001", // Avoid uppercase "orderAmount": 99.99, // Avoid camelCase } ``` ### 5. Batch Operations For scenarios requiring frequent user profile updates, use increment operations instead of multiple sets: ```go user := sensorswave.User{ LoginID: userID, } // Not recommended: multiple network requests client.ProfileSet(user, sensorswave.Properties{"login_count": 1}) client.ProfileSet(user, sensorswave.Properties{"points": 100}) // Recommended: single network request client.ProfileIncrement(user, sensorswave.Properties{ "login_count": 1, "points": 100, }) ``` --- ## FAQ ### Why aren't events being reported successfully? Check the following: 1. **Are Endpoint and Token correct**: Confirm `Endpoint` and `SourceToken` configurations are correct 2. **Network connection**: Check whether the server can access the reporting endpoint 3. **Error logs**: Check SDK error output; enable Logger for debugging 4. **Timeout settings**: Check if `HTTPTimeout` settings are reasonable 5. **Concurrency limits**: Check if `HTTPConcurrency` needs adjustment ### How do I avoid blocking the main business flow? The SDK uses internal asynchronous flushing, but it is recommended to handle errors in a separate goroutine: ```go go func() { if err := client.TrackEvent(user, "EventName", props); err != nil { log.Printf("Failed to track event: %v", err) } }() ``` ### Why are A/B testing evaluation results inconsistent? Ensure: 1. **Consistent user properties**: Use the same `ABUserProperties` for each evaluation 2. **Use sticky handler**: For scenarios requiring consistency, configure `StickyHandler` 3. **Metadata is updated**: Check `MetaLoadInterval` settings to ensure metadata has been refreshed --- ## Related Documentation - [Tracking Strategy](../tracking-strategy.mdx): Understand the pros and cons of server-side and client-side tracking - [How to Properly Identify Users](../user-identification.mdx): Best practices for user identity recognition - [Data Model](../data-model.mdx): Understand the Sensors Wave data structure - [Events and Properties](../events-and-properties.mdx): Event design guidelines and best practices --- **Last updated**: January 19, 2026 **SDK Repository**: [github.com/sensorswave/sdk-go](https://github.com/sensorswave/sdk-go) **License**: See LICENSE file