Skip to main content

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.

note

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:

FieldTypeRequiredDescription
UserIdstringYes*Unique identifier for the user
AnonymousIdstringYes*Anonymous identifier (if UserId not available)
Traitsanalytics.TraitsNoUser attributes (email, name, company, etc.)
Context*analytics.ContextNoExtra context (ip, userAgent, campaign, etc.)
Timestamptime.TimeNoWhen the event occurred (defaults to now)
Integrationsanalytics.IntegrationsNoControl 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:

FieldTypeRequiredDescription
UserIdstringYes*Unique identifier for the user
AnonymousIdstringYes*Anonymous identifier
EventstringYesName of the action being tracked
Propertiesanalytics.PropertiesNoProperties describing the event
Context*analytics.ContextNoExtra context
Timestamptime.TimeNoWhen the event occurred
Integrationsanalytics.IntegrationsNoControl 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:

FieldTypeRequiredDescription
UserIdstringYes*Unique identifier for the user
AnonymousIdstringYes*Anonymous identifier
NamestringNoName of the page
Propertiesanalytics.PropertiesNoPage properties (url, referrer, title, etc.)
Context*analytics.ContextNoExtra context
Timestamptime.TimeNoWhen the page was viewed
Integrationsanalytics.IntegrationsNoControl 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:

FieldTypeRequiredDescription
UserIdstringYes*Unique identifier for the user
AnonymousIdstringYes*Anonymous identifier
GroupIdstringYesUnique identifier for the group/company
Traitsanalytics.TraitsNoGroup attributes
Context*analytics.ContextNoExtra context
Timestamptime.TimeNoWhen the association occurred
Integrationsanalytics.IntegrationsNoControl 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:

FieldTypeRequiredDescription
UserIdstringYesThe new user ID
PreviousIdstringYesThe previous ID (UserId or AnonymousId)
Context*analytics.ContextNoExtra context
Timestamptime.TimeNoWhen the alias occurred
Integrationsanalytics.IntegrationsNoControl 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:

FieldTypeRequiredDescription
UserIdstringYes*Unique identifier for the user
AnonymousIdstringYes*Anonymous identifier
NamestringNoName of the screen
Propertiesanalytics.PropertiesNoScreen properties
Context*analytics.ContextNoExtra context
Timestamptime.TimeNoWhen the screen was viewed
Integrationsanalytics.IntegrationsNoControl 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
}
OptionTypeDefaultDescription
Endpointstringhttps://api.dreamdata.cloudAPI endpoint
Intervaltime.Duration5sTime to wait before flushing queued messages
BatchSizeint250Number of messages to queue before sending (set to 1 to disable batching)
VerboseboolfalseEmit more frequent and detailed log messages
Transporthttp.RoundTripperhttp.DefaultTransportCustomize how requests are sent at the HTTP level
Loggeranalytics.Loggerstderr loggerWhere the client writes info and error logs
Callbackanalytics.CallbacknilNotified when a message succeeds or fails to send
DefaultContext*analytics.ContextnilContext applied to every message sent by the client
RetryAfterfunc(int) time.Durationbackoff policyHow 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.comapp.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:

OptionValuePurpose
Domain.example.comLeading dot shares cookie across subdomains
Path/Available on all paths
SameSiteLaxPrevents CSRF while allowing navigation
SecuretrueOnly sent over HTTPS
HttpOnlytrueNot accessible via JavaScript (more secure)

Cross-TLD Tracking

Tracking across different top-level domains (e.g., example.comexample.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
}
Privacy Consideration

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

  1. Always handle errors - Check the error returned by Enqueue and attach a Callback to catch and log send failures
  2. Use graceful shutdown - Call Close() before your application exits
  3. Set appropriate batch settings - Balance between real-time data and efficiency
  4. Include context - Add IP addresses and user agents for better attribution
  5. Use consistent user IDs - Maintain the same UserId across 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()
}