Skip to content

serverTracker.fromContext()

Hydrates a TrackbridgeContext envelope (typically produced by tracker.exportContext() on the browser) into a tracker bound to its identifiers, consent, and PII. The bound tracker exposes the same call surface as the unbound one — trackEvent, trackConversion, plus the five helpers — with envelope-derived fields filled in by default.

For the why and the end-to-end pattern, see The context envelope.

serverTracker.fromContext(envelope: TrackbridgeContext): ContextBoundServerTracker;
type ContextBoundServerTracker = {
trackEvent(input: BoundServerEventInput): Promise<ServerEventResult>;
trackConversion(input: BoundServerConversionInput): Promise<ServerConversionResult>;
trackPurchase(input: BoundPurchaseInput): Promise<ServerHelperResult>;
trackBeginCheckout(input?: BoundBeginCheckoutInput): Promise<ServerHelperResult>;
trackAddToCart(input?: BoundAddToCartInput): Promise<ServerHelperResult>;
trackSignUp(input?: BoundSignUpInput): Promise<ServerHelperResult>;
trackRefund(input: BoundRefundInput): Promise<ServerHelperResult>;
};

The Bound*Input types are the same as the unbound input types with clientId made optional. The bound tracker fills in clientId from envelope.clientId when omitted.

For each call on the bound tracker:

  1. clientId: per-call value wins, otherwise envelope.clientId. If neither supplies one, the call throws synchronously.
  2. gclid, gbraid, wbraid: per-call value wins per field. Envelope values fill in the rest.
  3. userId: per-call value wins, otherwise envelope.userId.
  4. userData: per-call value replaces envelope.userData entirely. There is no deep merge — partial overrides require the caller to spread.
  5. consent: per-call value wins. The envelope’s consent snapshot is treated as ServerConsent defaults.
  6. All other fields (transactionId, value, currency, items, etc.) come exclusively from the per-call input.

Throws synchronously if the envelope is malformed:

  • Unknown v (currently anything other than 1).
  • Missing clickIds or consent.
  • Other shape violations.

These are programming errors, not runtime issues — envelopes are opaque payloads, not user-editable.

app/api/webhooks/stripe/route.ts
import { serverTracker } from '@/lib/tracker.server';
export async function POST(req: Request) {
const event = await verifyStripeSignature(req);
if (event.type !== 'checkout.session.completed') return new Response();
const order = await db.orders.findByStripeId(event.data.object.id);
const bound = serverTracker.fromContext(order.trackbridgeContext);
const result = await bound.trackPurchase({
transactionId: order.id,
value: order.total,
currency: order.currency,
items: order.items,
// clientId, gclid, userData all default from the envelope
});
if (!result.ads.ok && !('skipped' in result.ads)) reportError(result.ads.error);
return new Response();
}

To override one field of userData without losing the rest, spread:

await bound.trackPurchase({
transactionId: order.id,
value: order.total,
currency: order.currency,
items: order.items,
userData: {
...order.trackbridgeContext.userData,
email: order.customer.updatedEmail, // overrides only the email
},
});