Go SDK
The Dreamdata Go SDK (github.com/dreamdata-io/analytics-go) provides a robust
way to track events from your server-side applications. It handles batching,
retries, and graceful shutdown automatically.
Runtime Support
The SDK is compatible with:
- Go >= 1.17
Prerequisites
Before you begin, ensure you have:
- A Dreamdata account
- Your Dreamdata Source API Key (found in
Data Platform > Sources > Server Side Analytics APIs)
Installation
Install the SDK with go get:
go get github.com/dreamdata-io/analytics-go
Then import it in your code:
import "github.com/dreamdata-io/analytics-go"
We recommend using Go modules (or a vendoring tool) to pin the version and avoid issues related to API breaking changes introduced between major versions of the library.
Quick Start
package main
import (
"github.com/dreamdata-io/analytics-go"
)
func main() {
// Initialize the Analytics client
client := analytics.New("<YOUR_DREAMDATA_API_KEY>")
// Flush queued messages and close the client on exit
defer client.Close()
// Track an event
client.Enqueue(analytics.Track{
UserId: "user-123",
Event: "Order Completed",
Properties: analytics.NewProperties().
Set("orderId", "order-456").
SetRevenue(99.99),
})
// Identify a user
client.Enqueue(analytics.Identify{
UserId: "user-123",
Traits: analytics.NewTraits().
SetEmail("user@example.com").
SetName("Jane Doe").
Set("plan", "enterprise"),
})
}
Every call is queued in memory and sent in the background — see
Batching for details. Always call Close() before your process
exits so queued messages are flushed.
API Methods
You send every message type through a single Enqueue method, passing one of
the message structs below (analytics.Track, analytics.Identify, and so on).
Enqueue returns an error if the client was already closed or the message was
malformed.
For brevity, most snippets below assume an initialized client and omit the
surrounding package and import declarations. Add the imports as needed.
Identify
Associates a user with their traits and records when they are first recognized.
client.Enqueue(analytics.Identify{
UserId: "user-123",
Traits: analytics.NewTraits().
SetEmail("user@example.com").
SetName("Jane Doe").
Set("company", "Acme Inc").
Set("plan", "enterprise"),
})
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
UserId | string | Yes* | Unique identifier for the user |
AnonymousId | string | Yes* | Anonymous identifier (if UserId not available) |
Traits | analytics.Traits | No | User attributes (email, name, company, etc.) |
Context | *analytics.Context | No | Extra context (ip, userAgent, campaign, etc.) |
Timestamp | time.Time | No | When the event occurred (defaults to now) |
Integrations | analytics.Integrations | No | Control which integrations receive this event |
*At least one of UserId or AnonymousId is required.
Track
Records actions your users perform.
client.Enqueue(analytics.Track{
UserId: "user-123",
Event: "Article Read",
Properties: analytics.NewProperties().
SetTitle("Getting Started with B2B Analytics").
SetCategory("Documentation").
Set("readTime", 245),
})
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
UserId | string | Yes* | Unique identifier for the user |
AnonymousId | string | Yes* | Anonymous identifier |
Event | string | Yes | Name of the action being tracked |
Properties | analytics.Properties | No | Properties describing the event |
Context | *analytics.Context | No | Extra context |
Timestamp | time.Time | No | When the event occurred |
Integrations | analytics.Integrations | No | Control integrations |
*At least one of UserId or AnonymousId is required.
Page
Records page views on your website.
client.Enqueue(analytics.Page{
UserId: "user-123",
Name: "API Reference",
Properties: analytics.NewProperties().
SetURL("https://example.com/docs/api").
SetReferrer("https://google.com"),
})
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
UserId | string | Yes* | Unique identifier for the user |
AnonymousId | string | Yes* | Anonymous identifier |
Name | string | No | Name of the page |
Properties | analytics.Properties | No | Page properties (url, referrer, title, etc.) |
Context | *analytics.Context | No | Extra context |
Timestamp | time.Time | No | When the page was viewed |
Integrations | analytics.Integrations | No | Control integrations |
*At least one of UserId or AnonymousId is required.
Group
Associates a user with a company or organization.
client.Enqueue(analytics.Group{
UserId: "user-123",
GroupId: "company-456",
Traits: analytics.NewTraits().
SetName("Acme Inc").
Set("industry", "Technology").
Set("employees", 150).
Set("plan", "enterprise"),
})
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
UserId | string | Yes* | Unique identifier for the user |
AnonymousId | string | Yes* | Anonymous identifier |
GroupId | string | Yes | Unique identifier for the group/company |
Traits | analytics.Traits | No | Group attributes |
Context | *analytics.Context | No | Extra context |
Timestamp | time.Time | No | When the association occurred |
Integrations | analytics.Integrations | No | Control integrations |
*At least one of UserId or AnonymousId is required.
Alias
Merges two user identities, connecting a known user to an anonymous one.
client.Enqueue(analytics.Alias{
UserId: "user-123",
PreviousId: "anon-xyz-789",
})
Note that when testing this method it won't show up in the debugger since it's not an event, but a way to link users with an id.
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
UserId | string | Yes | The new user ID |
PreviousId | string | Yes | The previous ID (UserId or AnonymousId) |
Context | *analytics.Context | No | Extra context |
Timestamp | time.Time | No | When the alias occurred |
Integrations | analytics.Integrations | No | Control integrations |
Screen
Records screen views in mobile or desktop applications.
client.Enqueue(analytics.Screen{
UserId: "user-123",
Name: "Profile",
Properties: analytics.NewProperties().
Set("variation", "dark-mode"),
})
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
UserId | string | Yes* | Unique identifier for the user |
AnonymousId | string | Yes* | Anonymous identifier |
Name | string | No | Name of the screen |
Properties | analytics.Properties | No | Screen properties |
Context | *analytics.Context | No | Extra context |
Timestamp | time.Time | No | When the screen was viewed |
Integrations | analytics.Integrations | No | Control integrations |
*At least one of UserId or AnonymousId is required.
Building Traits and Properties
Traits, Properties, and Integrations are free-form maps, so you can set
any value you see fit. The SDK provides chainable helpers for common fields, and
a generic Set for everything else:
traits := analytics.NewTraits().
SetEmail("user@example.com").
SetFirstName("Jane").
SetLastName("Doe").
Set("plan", "enterprise")
properties := analytics.NewProperties().
SetRevenue(99.99).
SetCurrency("USD").
Set("orderId", "order-456")
You can also pass a plain map[string]interface{} directly if you prefer.
Configuration
Use analytics.NewWithConfig to customize the client. It returns an error if
the configuration contains impossible values (such as a negative interval):
client, err := analytics.NewWithConfig("<YOUR_DREAMDATA_API_KEY>", analytics.Config{
Endpoint: "https://api.dreamdata.cloud",
Interval: 10 * time.Second,
BatchSize: 250,
Verbose: false,
})
if err != nil {
// handle invalid configuration
}
| Option | Type | Default | Description |
|---|---|---|---|
Endpoint | string | https://api.dreamdata.cloud | API endpoint |
Interval | time.Duration | 5s | Time to wait before flushing queued messages |
BatchSize | int | 250 | Number of messages to queue before sending (set to 1 to disable batching) |
Verbose | bool | false | Emit more frequent and detailed log messages |
Transport | http.RoundTripper | http.DefaultTransport | Customize how requests are sent at the HTTP level |
Logger | analytics.Logger | stderr logger | Where the client writes info and error logs |
Callback | analytics.Callback | nil | Notified when a message succeeds or fails to send |
DefaultContext | *analytics.Context | nil | Context applied to every message sent by the client |
RetryAfter | func(int) time.Duration | backoff policy | How long to wait between retries of failed requests |
The client authenticates by sending your API key as the username in HTTP Basic auth — there is no extra setup required.
Batching
Every method you call does not result in an HTTP request, but is queued in memory instead. Messages are then flushed in batch in the background, which allows for much faster operation.
By default, the library will flush:
- Every 250 messages (controlled by
Config.BatchSize) - If 5 seconds have passed since the last flush (controlled by
Config.Interval) - There is a maximum of 500KB per batch request and 32KB per call
If you don't want to batch messages, you can turn batching off by setting the
BatchSize option to 1:
client, err := analytics.NewWithConfig("<YOUR_DREAMDATA_API_KEY>", analytics.Config{
BatchSize: 1,
})
Error Handling
Enqueue returns an error synchronously when a message is malformed or the
client has already been closed:
if err := client.Enqueue(analytics.Track{
UserId: "user-123",
Event: "Order Completed",
}); err != nil {
log.Printf("could not enqueue event: %v", err)
}
Because messages are sent in the background, attach a Callback to be notified
when a message succeeds or fails to reach the API:
type analyticsCallback struct{}
func (analyticsCallback) Success(m analytics.Message) {}
func (analyticsCallback) Failure(m analytics.Message, err error) {
log.Printf("analytics send failed: %v", err)
}
client, _ := analytics.NewWithConfig("<YOUR_DREAMDATA_API_KEY>", analytics.Config{
Callback: analyticsCallback{},
})
Graceful Shutdown
Call Close() before your application exits. It flushes all queued messages and
then closes the client:
client := analytics.New("<YOUR_DREAMDATA_API_KEY>")
defer client.Close()
For long-running services, flush on shutdown signals:
client := analytics.New("<YOUR_DREAMDATA_API_KEY>")
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-sigs
client.Close() // flushes queued messages
os.Exit(0)
}()
Short-Lived Processes
Short-lived processes (cron jobs, serverless functions, CLI tools) may terminate
before the background flush runs. The key is to send events immediately and
flush before exiting. Set BatchSize to 1 so each event is sent right away, and
always call Close():
func handler() error {
client, err := analytics.NewWithConfig("<YOUR_DREAMDATA_API_KEY>", analytics.Config{
BatchSize: 1, // Send immediately
})
if err != nil {
return err
}
defer client.Close() // Block until queued messages are sent
return client.Enqueue(analytics.Track{
UserId: "user-123",
Event: "Job Completed",
Properties: analytics.NewProperties().Set("jobName", "nightly-export"),
})
}
Generating Identifiers
For purely server-side integrations, you need to generate and manage user
identifiers yourself. The SDK requires either UserId or AnonymousId for
every event.
Using UserId
Use UserId when you have an authenticated user. This should be a stable
identifier from your system (database ID, auth provider ID, etc.):
client.Enqueue(analytics.Track{
UserId: user.ID, // From your auth system
Event: "Feature Used",
Properties: analytics.NewProperties().
Set("feature", "export"),
})
Generating AnonymousId
For anonymous visitors, generate a UUID and persist it across requests:
import "github.com/google/uuid"
// Generate a new anonymous ID
anonymousId := uuid.NewString() // e.g., "550e8400-e29b-41d4-a716-446655440000"
Maintaining Identity Across Requests
Store the AnonymousId in a session or cookie to maintain consistency:
import (
"net/http"
"github.com/google/uuid"
"github.com/dreamdata-io/analytics-go"
)
const anonymousCookie = "dd_anonymous_id"
func anonymousID(w http.ResponseWriter, r *http.Request) string {
if c, err := r.Cookie(anonymousCookie); err == nil {
return c.Value
}
id := uuid.NewString()
http.SetCookie(w, &http.Cookie{
Name: anonymousCookie,
Value: id,
Path: "/",
MaxAge: 365 * 24 * 60 * 60, // 1 year
})
return id
}
func productHandler(client analytics.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
client.Enqueue(analytics.Track{
AnonymousId: anonymousID(w, r),
Event: "Product Viewed",
Properties: analytics.NewProperties().
Set("productId", r.URL.Query().Get("id")),
})
w.WriteHeader(http.StatusOK)
}
}
Linking Anonymous to Known Users
When a user logs in, call Alias to connect their anonymous activity to their
known identity:
func login(client analytics.Client, w http.ResponseWriter, r *http.Request) {
user := authenticateUser(r)
anonID := anonymousID(w, r)
// Link anonymous ID to the authenticated user
client.Enqueue(analytics.Alias{
UserId: user.ID,
PreviousId: anonID,
})
// Future events use UserId
client.Enqueue(analytics.Identify{
UserId: user.ID,
Traits: analytics.NewTraits().
SetEmail(user.Email).
SetName(user.Name),
})
}
Cookieless Tracking (Company-Level Only)
For privacy-first tracking without individual identification, use a static placeholder and rely on IP-to-company lookup:
import "net"
client.Enqueue(analytics.Track{
AnonymousId: "cookieless", // Static placeholder
Event: "Page Viewed",
Context: &analytics.Context{
IP: net.ParseIP(clientIP), // Required for company attribution
},
})
See Server-Side Cookieless Tracking for complete implementation details.
Cross-Domain Tracking
When your application spans multiple domains or subdomains, you need to ensure
the AnonymousId persists across them to maintain a unified user journey.
Cross-Subdomain Tracking
To track users across subdomains (e.g., www.example.com → app.example.com),
set cookies at the top-level domain so they're shared:
http.SetCookie(w, &http.Cookie{
Name: "dd_anonymous_id",
Value: uuid.NewString(),
Domain: ".example.com", // Leading dot includes all subdomains
Path: "/",
MaxAge: 365 * 24 * 60 * 60, // 1 year
SameSite: http.SameSiteLaxMode,
Secure: true, // Requires HTTPS
HttpOnly: true,
})
Key cookie options:
| Option | Value | Purpose |
|---|---|---|
Domain | .example.com | Leading dot shares cookie across subdomains |
Path | / | Available on all paths |
SameSite | Lax | Prevents CSRF while allowing navigation |
Secure | true | Only sent over HTTPS |
HttpOnly | true | Not accessible via JavaScript (more secure) |
Cross-TLD Tracking
Tracking across different top-level domains (e.g., example.com → example.io)
requires explicit coordination since cookies cannot be shared across TLDs.
Pass the AnonymousId via URL when linking between domains, then read it on the
target domain and store it in a cookie:
// On source domain (example.com)
func goToPartner(w http.ResponseWriter, r *http.Request, anonID string) {
target, _ := url.Parse("https://example.io/landing")
q := target.Query()
q.Set("dd_anon_id", anonID)
target.RawQuery = q.Encode()
http.Redirect(w, r, target.String(), http.StatusFound)
}
// On target domain (example.io)
func adoptAnonID(w http.ResponseWriter, r *http.Request) string {
urlAnonID := r.URL.Query().Get("dd_anon_id")
if c, err := r.Cookie("dd_anonymous_id"); err == nil {
return c.Value // Already known on this domain
}
if urlAnonID == "" {
urlAnonID = uuid.NewString()
}
http.SetCookie(w, &http.Cookie{
Name: "dd_anonymous_id",
Value: urlAnonID,
Domain: ".example.io",
Path: "/",
MaxAge: 365 * 24 * 60 * 60,
SameSite: http.SameSiteLaxMode,
Secure: true,
})
return urlAnonID
}
Cross-TLD tracking via URL parameters may expose the anonymous ID in browser history, logs, and referrer headers. Consider:
- Using short-lived tokens instead of raw IDs
- Implementing server-side redirects to strip parameters
- Ensuring compliance with privacy regulations
Best Practices
- Always handle errors - Check the error returned by
Enqueueand attach aCallbackto catch and log send failures - Use graceful shutdown - Call
Close()before your application exits - Set appropriate batch settings - Balance between real-time data and efficiency
- Include context - Add IP addresses and user agents for better attribution
- Use consistent user IDs - Maintain the same
UserIdacross your backend services
Example
package main
import (
"os"
"github.com/dreamdata-io/analytics-go"
)
func main() {
// Instantiates a client to send messages to the Dreamdata API.
client := analytics.New(os.Getenv("DREAMDATA_API_KEY"))
var err error
err = client.Enqueue(analytics.Identify{
UserId: "019mr8mf4r",
Traits: analytics.NewTraits().
SetEmail("user@dreamdata.io").
SetName("user").
SetAge(25),
})
if err != nil {
// do something with your err
}
err = client.Enqueue(analytics.Track{
UserId: "019mr8mf4r",
Event: "Song Played",
Properties: analytics.NewProperties().
SetName("My song").
Set("artist", "My artist"),
})
if err != nil {
// do something with your err
}
err = client.Enqueue(analytics.Identify{
UserId: "971mj8mk7p",
Traits: analytics.NewTraits().
SetEmail("user2@dreamdata.io").
SetName("user2").
SetAge(26),
})
if err != nil {
// do something with your err
}
err = client.Enqueue(analytics.Track{
UserId: "971mj8mk7p",
Event: "Song Played",
Properties: analytics.NewProperties().
SetName("My song").
Set("artist", "My artist"),
})
if err != nil {
// do something with your err
}
// Flushes any queued messages and closes the client.
client.Close()
}