Quick start
Five minutes from bun add to a deduplicated conversion. This page assumes you already have a Google Ads conversion ID and a GA4 measurement ID. If you don’t, the Google Ads OAuth guide covers obtaining the secrets you need on the server side.
1. Install both packages
Section titled “1. Install both packages”bun add @trackbridge/browser @trackbridge/serverpnpm add @trackbridge/browser @trackbridge/servernpm install @trackbridge/browser @trackbridge/serverThe two packages are versioned together. Install both even if you think you only need one — the dual-send pattern is the point of the SDK.
2. Create the browser tracker
Section titled “2. Create the browser tracker”import { createBrowserTracker } from '@trackbridge/browser';
export const tracker = createBrowserTracker({ adsConversionId: process.env.NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_ID!, ga4MeasurementId: process.env.NEXT_PUBLIC_GA4_MEASUREMENT_ID!, consentMode: 'v2', debug: process.env.NODE_ENV !== 'production',});The example uses process.env.NEXT_PUBLIC_*. For Vite use import.meta.env.VITE_*; for SvelteKit and Astro use import.meta.env.PUBLIC_*. Whatever the framework, the token must be inlined into the client bundle — these two values are public.
consentMode: 'v2' is the right default for any site that operates under GDPR or CCPA. Set it to 'off' only if you have an explicit reason. With v2, click-identifier cookies are gated on ad_storage consent — see Consent Mode v2.
3. Create the server tracker
Section titled “3. Create the server tracker”import { createServerTracker } from '@trackbridge/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', }, }, debug: process.env.NODE_ENV !== 'production',});Import this only from server-side code: API routes, server actions, webhook handlers. The five GOOGLE_* and GA4_API_SECRET values must never reach the browser bundle.
The keys in conversionActions are your own labels. The values are full Ads API resource names of the form customers/{customerId}/conversionActions/{actionId} — see Mapping conversion actions.
4. Fire the conversion from both sides
Section titled “4. Fire the conversion from both sides”The same call shape on the browser and on the server, with the same transactionId. Google sees both, deduplicates them on the transaction ID, and counts the conversion once.
'use client';import { useEffect } from 'react';import { tracker } from '@/lib/tracker.client';
export function FireConversion({ order }: { order: Order }) {useEffect(() => { tracker.trackConversion({ label: 'purchase', value: order.total, currency: 'USD', transactionId: order.id, userData: { email: order.customer.email, }, });}, [order]);return null;}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);
await serverTracker.trackConversion({ label: 'purchase', value: order.total, currency: 'USD', transactionId: order.id, gclid: order.gclid, userData: { email: order.customer.email, },});
return new Response();}The transactionId is the dedup key. Pass order.id (or any stable string you own) on both sides. Do not generate a UUID per call — different IDs cause Google to count the conversion twice. If you omit transactionId, the SDK auto-generates one and prints a warning, because dual-send is disabled for that call.
The userData is passed raw. The SDK normalizes (lowercase, NFC, trim) and SHA-256 hashes the fields Google requires hashed, identically on both sides. Never hash in your own code.
5. Verify it fires
Section titled “5. Verify it fires”In debug: true mode, the SDK is loud about both successes and failures.
Browser side. Open DevTools → Network, filter for googleadservices.com and google-analytics.com. After the conversion, you should see a collect request to GA4 and a gtag conversion request to Ads with the transaction_id you passed.
Server side. The Ads API returns 200 OK on accepted batches. With debug: true, any non-2xx response is logged with the response body so you can see what Google rejected.
End-to-end attribution into the Ads UI takes up to 24 hours. During development, watch the network requests — that’s the contract.
- The dual-send pattern — why both sides fire and what dedup actually does.
- Mapping conversion actions — the gtag-label / Ads-API-resource-name mapping in detail.
- Setting up Google Ads OAuth — if
GOOGLE_ADS_REFRESH_TOKENis still empty. - Wiring with a CMP — once consent is in scope.