createServerTracker()
The server tracker. Sends GA4 events via the Measurement Protocol and Google Ads conversions via the Ads API. Manages the OAuth refresh-token / access-token cycle internally — you give it a refresh token once, it handles the rest.
Import only from server-side code. The credentials this takes must never reach the browser.
Signature
Section titled “Signature”import { createServerTracker } from '@trackbridge/sdk/server';
function createServerTracker(config: ServerTrackerConfig): ServerTracker;ServerTrackerConfig
Section titled “ServerTrackerConfig”type ServerTrackerConfig = { ga4MeasurementId: string; ga4ApiSecret: string; ads?: ServerAdsConfig; debug?: boolean; fetch?: typeof globalThis.fetch; now?: () => number; generateTransactionId?: () => string; conversionLabels?: { purchase?: string; beginCheckout?: string; addToCart?: string; signUp?: string; };};
type ServerAdsConfig = { developerToken: string; customerId: string; refreshToken: string; clientId: string; clientSecret: string; loginCustomerId?: string; conversionActions: Record<string, string>; /** Default: 'v17'. */ apiVersion?: string;};| Field | Required | Notes |
|---|---|---|
ga4MeasurementId | yes | The GA4 property of the form G-XXXXXXXXXX. |
ga4ApiSecret | yes | The Measurement Protocol API secret minted in GA4 Admin → Data streams. |
ads | no | Required to call serverTracker.trackConversion. Omit for GA4-events-only setups. |
ads.developerToken | yes (if ads) | Google Ads developer token from your manager (MCC) account. |
ads.customerId | yes (if ads) | Customer ID, digits only — 1234567890, not 123-456-7890. |
ads.refreshToken | yes (if ads) | Long-lived OAuth refresh token. Best obtained from a Workspace account, not personal. |
ads.clientId | yes (if ads) | OAuth client ID from Google Cloud Console. |
ads.clientSecret | yes (if ads) | OAuth client secret. |
ads.loginCustomerId | no | Manager (MCC) ID, digits only. Only required if your MCC sits between the access-granted account and the customer account. |
ads.conversionActions | yes (if ads) | Mapping from your friendly labels to Ads API resource names. See Mapping conversion actions. |
ads.apiVersion | no, default 'v17' | The Ads API version segment in the request URL. |
debug | no, default false | When true, non-2xx responses and network errors are logged via console.warn. The auto-transactionId warning fires regardless. |
fetch | no, default globalThis.fetch | Test seam. |
now | no, default Date.now | Test seam. |
generateTransactionId | no | Test seam. Default: tb_${crypto.randomUUID()}. |
conversionLabels | no | Per-helper Ads conversion labels. Same shape as the browser-side config. Helper without an entry → fires GA4 only. refund is intentionally absent — Ads refund adjustments are out of scope in v1. |
Returns
Section titled “Returns”type ServerTracker = { // Generic fires trackEvent(input: ServerEventInput): Promise<ServerEventResult>; trackConversion(input: ServerConversionInput): Promise<ServerConversionResult>;
// Context-envelope hydration fromContext(envelope: TrackbridgeContext): ContextBoundServerTracker;
// Semantic helpers trackPurchase(input: ServerPurchaseInput): Promise<ServerHelperResult>; trackBeginCheckout(input: ServerBeginCheckoutInput): Promise<ServerHelperResult>; trackAddToCart(input: ServerAddToCartInput): Promise<ServerHelperResult>; trackSignUp(input: ServerSignUpInput): Promise<ServerHelperResult>; trackRefund(input: ServerRefundInput): Promise<ServerHelperResult>;};
type ServerEventResult = { ga4: SendResult };type ServerConversionResult = { ads: SendResult };type SendResult = { ok: true } | { ok: false; error: Error };
type ServerHelperResult = { ads: HelperSendResult; ga4: HelperSendResult };type HelperSendResult = | { ok: true } | { ok: false; error: Error } | { skipped: true; reason: 'no_label_configured' | 'refund_ads_unsupported' };trackEvent / trackConversion resolve to structured per-destination results. Runtime API failures (network, non-2xx, OAuth refresh) land on the error field; only configuration errors throw.
The five semantic helpers fan out to both Ads and GA4 and report on each independently — including a { skipped: true, reason } variant for cases where the helper deliberately did not call a destination (no Ads label configured, or trackRefund’s permanent Ads-skip).
fromContext(envelope) returns a ContextBoundServerTracker — same call surface, with envelope-derived defaults for clientId, click identifiers, userData, and consent.
See:
serverTracker.trackConversion()/serverTracker.trackEvent()- Helpers:
trackPurchase,trackBeginCheckout,trackAddToCart,trackSignUp,trackRefund - Envelope:
fromContext
Behavior on initialization
Section titled “Behavior on initialization”If ads is configured, the constructor wires an OAuth access-token provider (which will refresh on first use, then cache until ~60 seconds before expiry) and an Ads API client. No network requests fire at init — the first trackConversion call triggers the first refresh.
The constructor itself never throws on unreachable APIs or invalid credentials; those failures surface at call time.
Errors
Section titled “Errors”Throws synchronously at init if either of these is missing:
Error: [trackbridge] ga4MeasurementId is requiredError: [trackbridge] ga4ApiSecret is requiredtrackConversion throws at call time if ads is not configured, or if the label you pass isn’t in the conversionActions map. See trackConversion.
Example
Section titled “Example”import 'server-only';import { createServerTracker } from '@trackbridge/sdk/server';
export const serverTracker = createServerTracker({ ga4MeasurementId: process.env.NEXT_PUBLIC_GA4_MEASUREMENT_ID!, ga4ApiSecret: process.env.GA4_API_SECRET!, ads: { developerToken: process.env.GOOGLE_ADS_DEVELOPER_TOKEN!, customerId: process.env.GOOGLE_ADS_CUSTOMER_ID!, refreshToken: process.env.GOOGLE_ADS_REFRESH_TOKEN!, clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!, clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!, conversionActions: { purchase: 'customers/1234567890/conversionActions/9876543210', signup: 'customers/1234567890/conversionActions/1122334455', }, }, debug: process.env.NODE_ENV !== 'production',});The import 'server-only' line is the cheapest insurance against accidentally importing this from a client component. Next.js will fail the build; other frameworks have equivalent guards.
See also
Section titled “See also”- Setting up Google Ads OAuth — obtaining the five
ads.*values. - Mapping conversion actions — the
conversionActionsmap in detail.