What this lets you do
Send pageviews and revenue conversions to Traceten from your Go backend, over server-to-server HTTP that survives ad-blockers and privacy browsers. Use it where the browser snippet cannot run: server-rendered pages, background jobs, webhook handlers, and headless flows.Before you start
- Go 1.21 or newer.
- Your Traceten site id, from Sites → Install in the dashboard.
- Your ingest host URL, for example
https://ingest.traceten.com. - A
VisitorIDfor each event. The SDK runs on your server, so there is no Traceten cookie to read. You supply the identifier from your own request context. It is either a UUID or anh:<64-hex>identify hash. See supplying identifiers.
Install
Minimal example
How it behaves
Page and Track enqueue the event and return immediately, never blocking on the network. A background goroutine batches, retries, and flushes: when the events queue reaches the flush threshold, on the flush interval, on an explicit Flush, and on Close.
Neither method returns a delivery error. Delivery failures reach your WithOnError callback and are also returned by Flush. Both methods do return an error synchronously for a programmer mistake, so you catch a mis-shaped call before it enqueues:
Trackcalled without aVisitorID- an event name that fails its pattern
- a
URLthat is not a valid URL - a bad
Currencyor id charset
Configuration
host must be an absolute http(s) URL. A trailing slash is stripped. There is no default host: each deployment has its own ingest URL.
host must use https unless it is a loopback address. The API key is sent as a Bearer token on every request, so a plaintext http host would transmit a live credential in cleartext; the SDK refuses one at construction.
WithAPIKey is required. See The API key below.
The API key
WithAPIKey is required. Create a key in the dashboard under Settings, API keys, or use the key shown once when you created the site.
The key must carry the Send events permission (ingest:write). New keys are created read-only by default, so tick that box when you create one, or the API will reject your calls. The key created automatically with a new site already has it. See permissions.
Keep it on your server. It is a secret: never put it in client-side code, a mobile app, or a public repository. It is not the same value as the site id, which is public and already embedded in your pages.
The key does two things:
- It gets you in. The SDK posts to
{host}/v1/server/events,{host}/v1/server/conversionsand{host}/v1/server/payments, which return401without a valid key. The open/v1/eventsand/v1/conversionsendpoints stay open for the browser snippet, which cannot hold a secret. - It gets you your own quota. Authenticated traffic is rate-limited on a bucket tied to the key, separate from the shared per-site bucket. the site id is public, so anyone who can read your page source can send events under it. With a key, that traffic cannot exhaust your allowance and 429 your conversion calls.
New validates the key’s shape and returns ErrMissingAPIKey or a *ValidationError before the client starts. An integration that silently drops every event is worse than one that fails on its first line.
To rotate a key: create the new one, deploy it, then revoke the old one. Revocation normally takes effect at the edge within about a minute. If our
database is unreachable at that moment, an edge location that was already using
the key may keep honouring it for up to about fifteen minutes more, so that a
database blip cannot silently drop your events.
API reference
New(siteID, host string, opts ...Option) (*Client, error)
Returns ErrMissingSiteID, ErrMissingHost, ErrInvalidHost, ErrMissingAPIKey, or a *ValidationError for a bad siteID charset or a malformed API key.
Page(ev PageEvent) error
Enqueues a pageview to POST {host}/v1/server/events (camelCase wire fields). Returns an error only for a programmer or validation mistake, never for delivery.
Which user agent gets recorded. The
UserAgent you pass is what Traceten
stores on the event and classifies on. Omit it and the event falls back to the
User-Agent header the SDK’s own HTTP client sent. For Go that is the standard
library’s Go-http-client/1.1, which describes your server rather than the
visitor. That substitution applies only to requests authenticated with an API
key; the browser snippet cannot override its own User-Agent this way.
Track(name string, ev TrackEvent) error
Enqueues a conversion to POST {host}/v1/server/conversions (snake*case wire fields: event_name, value_cents, visitor_id). name must match ^[a-z]a-z0-9*]\*$(no hyphen).VisitorID is required. Both are validated synchronously: a bad call returns an error and enqueues nothing.
ValueCents is in minor units. For $49.00, set it to 4900. The field is spelled out as ValueCents (not Value) to prevent the dollars-versus-cents mistake. It is a pointer so a real 0 is distinguishable from unset.
Goal(name string, ev TrackEvent) error
TrackEvent, same endpoint, and same payload as Track, with one difference: it returns a *ValidationError on the reserved names, which belong to the Stripe and Shopify integrations, and enqueues nothing.
Reserved: payment, free_trial, trial_started, trial_converted, subscription_started, subscription_upgraded, subscription_downgraded, subscription_renewed, subscription_cancel_scheduled, subscription_reactivated, subscription_ended. Track still accepts them, because that is how those events are legitimately sent.
Goal property keys and values are both stored, and both are read back: GET /v1/goals/{name}/properties returns each key and its most common values, and the dashboard renders the same breakdown. Keep them to ids and enums. See what happens to property keys and values.
Payment(ctx context.Context, p Payment) (PaymentResult, error)
Records a payment from any payment processor via POST {host}/v1/server/payments. See the Payment API reference for the full contract.
Unlike Page and Track, this one blocks until the server answers and returns what it did with the payment.
Amount is the major unit, the opposite of Track’s ValueCents. For $49.99 pass 49.99, and for ¥5000 pass 5000. It is the number you read off your processor.
If Currency isn’t one Traceten has a reference rate for, set SettlementAmount/SettlementCurrency to the processor’s own conversion of the payment into a currency it does price (for example, a payout figure), and the payment is recorded off that instead of being dropped. Both must be set together; if only one is set, neither is sent.
Partial refunds do not go through this method.
Refunded: true takes back everything still
outstanding on the transaction, and this SDK has no field for a smaller figure. To refund part of
a payment, POST to /v1/server/payments with refunded_amount, which is
the total refunded against the transaction so far.TransactionID returns "duplicate" and creates nothing. That is why the SDK retries a 5xx here.
It returns a *ValidationError for an invalid field, and a delivery error when the payment could not be sent after every retry. It is the only method in this SDK that returns a delivery failure to the caller, because a dropped payment is missing revenue rather than a missing pageview.
Flush(ctx context.Context) error
ctx cancellation.
Close() error
Batching and retries
- Pageviews are sent to
/v1/server/eventsin batch envelopes of up to 50 events. If you enqueue 120 pageviews, the SDK sends them as batches of 50, 50, and 20. - Conversions are sent to
/v1/server/conversionsone body per event. - On a 5xx, a 429, or a network or timeout error, the batch is retried with
200ms * 2^attemptfull-jitter backoff, up toWithMaxRetriestimes. - On any other 4xx the payload is malformed. The SDK drops it and calls
WithOnError. It does not retry, because retrying a rejected payload forever is a self-inflicted denial of service.
Flush on exit
Go has no reliableatexit. Close is the flush-on-exit mechanism. Call it, usually defer client.Close(), before the process exits so buffered events are delivered. Data loss on a hard kill -9 is expected. Data loss on a graceful shutdown is not, provided you called Close.
Errors
New,Page, andTrackreturn anerrorsynchronously for programmer mistakes:ErrMissingSiteID,ErrMissingHost,ErrInvalidHost,ErrMissingVisitorID,ErrClosed, and*ValidationError(bad URL, event name, currency, or ids).- Delivery failures never come back from
PageorTrack. They reachWithOnErrorand are also returned byFlush. TheOnErrorcallback may run on multiple goroutines, so keep it concurrency-safe.
Verify it worked
- Send one
Pagecall from your backend. - Open Sites → Events in the Traceten dashboard for that site.
- The event appears in the live event feed within a few seconds.
WithOnError to see why.
Verify webhook signatures
If you subscribe to Traceten’s outbound webhooks (AI-session and conversion events), verify every delivery before you act on it.VerifyWebhook recomputes the X-Traceten-Signature HMAC in constant time (hmac.Equal), enforces the replay window, and returns the parsed *WebhookEvent, or an error wrapping ErrSignatureVerification.
Verify the raw body, not a re-serialized one
The single most common verification bug: you decode the JSON, re-encode it, and the bytes no longer match what Traceten signed (key order, whitespace, and unicode escaping all differ). Read the body withio.ReadAll before any JSON decoding, and only act on the event after verification succeeds.
traceten.WithTolerance(seconds) to change the 300-second replay window (pass math.Inf(1) only when testing against the fixed conformance vector), and traceten.WithNow(seconds) to inject the clock in tests.
Troubleshooting
Events never appear. RegisterWithOnError. The most common causes are a wrong host, a site id typo, or a firewall blocking outbound HTTPS.
Error from Track. You omitted VisitorID, or the event name has a hyphen. Conversion names allow only a-z, 0-9, and _.
Error from Page. The URL is not a valid absolute URL, or the EventName does not match ^[a-z][a-z0-9_-]*$.
A short-lived program exits before sending. Ensure defer client.Close() runs, or call client.Flush(ctx) before returning from main.
Next
- Node.js SDK
- Python SDK
- Browser API reference for client-side tracking
- Stripe revenue attribution

