trackbridge-conversions
The conversion-wiring skill. Implements the dual-send pattern: same call shape on browser and server, same transactionId, click identifiers forwarded across the boundary. Refuses anti-patterns (per-side UUID generation, pre-hashing, single-side-only fires) when it sees them.
Frontmatter
Section titled “Frontmatter”name: trackbridge-conversionsdescription: Use when firing a Trackbridge conversion event (purchase, signup, lead) — placing trackConversion calls in client checkout-success handlers and the corresponding server webhooks or order-confirmation handlers, including click-identifier forwarding and dedup via shared transactionId.When it triggers
Section titled “When it triggers”- “Wire up purchase conversions”
- “Add a
trackConversioncall to my Stripe webhook” - “Fire a conversion when this signup form submits”
- “Forward
gclidto my server” - Any mention of
trackConversion, dual-send, dedup, or the click-identifier forwarding pattern
Prerequisites
Section titled “Prerequisites”- Both trackers already created via
trackbridge-setup—lib/tracker.client.tsandlib/tracker.server.tsexist. - Server-side credentials filled in via
trackbridge-ads-oauth—GA4_API_SECRET,GOOGLE_*env vars are populated and theconversionActionsmap has at least one entry. - An order/transaction object with a stable ID available on both client and server.
- For click-identifier forwarding: a way for the client to POST to the server before the conversion fires (typically the same request that creates the order).
What it touches
Section titled “What it touches”No new files created. Edits:
- The browser-side checkout-success handler (success page, post-payment effect, redirect handler — wherever the user lands after payment).
- The server-side conversion handler (webhook handler, order-confirmation API route, server action).
- Your order schema, only if you confirm you want click-ID forwarding and the columns aren’t there yet (
gclid,gbraid,wbraidas nullable text columns).
What it asks
Section titled “What it asks”- Where to place the browser-side call (path or component).
- Where to place the server-side call (webhook handler, API route, server action — full file path).
- Whether you need click-identifier forwarding (defaults: yes, if you don’t already have it).
- The conversion
label(must exist in theconversionActionsmap; the skill checks before generating the call).
What it won’t do
Section titled “What it won’t do”- Set up Google Ads API secrets. See
trackbridge-ads-oauth. - Wire a CMP. See
trackbridge-consent. - Author webhook authentication or payload parsing. The skill assumes
verifyStripeSignature(or your equivalent) already exists. - Generate a fresh
transactionIdper call. It refuses; the dual-send pattern requires a stable ID you control.
Anti-patterns it refuses
Section titled “Anti-patterns it refuses”- Different
transactionIds per side. If you ask forDate.now()orcrypto.randomUUID()on either side, the skill stops and explains: that defeats dedup. The skill insists onorder.id(or equivalent stable string). - Pre-hashing identity fields. If you try to pass already-hashed email or phone, the skill explains the SDK does this — and your hash will diverge from Google’s expectations.
- Single-side-only fires. The skill always wires both sides. If you only have one (e.g., no server endpoint for the conversion), it stops and recommends adding one before continuing — single-side defeats the SDK’s purpose.
tracker.serverimported from a client component. The skill watches for the server tracker leaking into client code and refuses to add the import there; instead it routes through an API endpoint.
What it produces
Section titled “What it produces”Browser side (in your success-page component or post-payment effect):
import { tracker } from '@/lib/tracker.client';
await tracker.trackConversion({ label: 'purchase', value: order.total, currency: 'USD', transactionId: order.id, userData: { email: order.customer.email, phone: order.customer.phone, firstName: order.customer.firstName, lastName: order.customer.lastName, address: { street: order.billingAddress.street, city: order.billingAddress.city, region: order.billingAddress.region, postalCode: order.billingAddress.postalCode, country: order.billingAddress.country, }, },});Server side (in your webhook handler):
import 'server-only';import { serverTracker } from '@/lib/tracker.server';
await serverTracker.trackConversion({ label: 'purchase', value: order.total, currency: 'USD', transactionId: order.id, gclid: order.gclid, gbraid: order.gbraid, wbraid: order.wbraid, userData: { /* same shape as browser */ },});Plus, if click-ID forwarding wasn’t already in place: edits to your checkout API to capture and store the click identifiers from the client.
Example
Section titled “Example”You: Wire up purchase conversions for my Stripe checkout.
Claude (router → conversions): I see you have lib/tracker.client.tsand lib/tracker.server.ts already (from trackbridge-setup), andGOOGLE_ADS_REFRESH_TOKEN is filled in (from trackbridge-ads-oauth).
Two questions: 1. Where's the browser-side success page? (e.g., app/checkout/ success/page.tsx) 2. Where's the Stripe webhook handler? (e.g., app/api/webhooks/ stripe/route.ts)
You: app/(checkout)/success/page.tsx and app/api/webhooks/stripe/ route.ts.
Claude: Got it. Do you currently store gclid/gbraid/wbraid on theorder? If not, I'll add click-ID forwarding too — three nullable textcolumns plus capture in your checkout API.
You: Don't have it.
Claude: Adding the migration and the capture path: - lib/db/schema.ts: + gclid, gbraid, wbraid columns on orders - app/api/checkout/route.ts: capture clickIds from request body - app/checkout/page.tsx: client reads tracker.getClickIdentifiers() and forwards on submit
Then the conversions: - app/(checkout)/success/page.tsx: tracker.trackConversion(...) in a useEffect - app/api/webhooks/stripe/route.ts: serverTracker.trackConversion (...) on checkout.session.completed, gated on conversionFiredAt for idempotency
The transactionId on both sides is order.id. Same value, dedup works.See also
Section titled “See also”- Stripe webhook recipe — the prose version, with full schema and idempotency.
- The dual-send pattern
- Mapping conversion actions — keeping the labels in sync.