Ads attribution and conversion measurement

The sales site is designed for ChatGPT Ads traffic: ad click → product landing → Free Lite, waitlist or Pro checkout. Tracking code is present; no advertising account, Pixel, analytics destination or Conversions API transport is connected by default. The starter applications do not inherit marketing tracking.

Implemented local behavior

apps/landing/src/tracking/preserveAdParams.js allowlists oppref, olref, UTM fields and campaign/ad IDs. It preserves their opaque values through internal links and explicitly configured checkout/waitlist origins, including existing target query parameters and fragments. It does not copy unrelated query fields such as an email or reset token to providers. Incoming click references are preserved; do not pre-populate reserved click references in Ads Manager destination URLs.

Attribution stays in memory and URLs by default. Optional sessionStorage persistence lasts 30 minutes and is enabled only after measurement consent. Storage errors fall back to URL propagation. The footer measurement control can allow or decline optional measurement for the session. Adapt the consent wording/provider disclosures to the final deployment; no provider SDK or automatic advanced matching is loaded here.

trackEvent(eventName, payload) creates a shared event ID and calls installed adapters only with consent. Development mode (TRACKING_MODE=dev) keeps a bounded in-memory log and concise console output. It does not log contact details or raw click references. Default off disables this development log; provider sending additionally requires consent and each provider's configuration/SDK. Unconfigured adapters return explicit unavailable states.

Event plan and actual triggers

Internal event Trigger OpenAI mapping
page_view_starter_landing Product landing loads page_viewed / contents
click_free_lite User follows a Free Lite CTA custom
download_lite Successful ZIP response received by browser, then save action started custom
submit_waitlist Trusted signup integration confirms accepted submission lead_created / customer_action
click_pro_early_access User selects Pro CTA, even if checkout still unavailable custom
click_checkout Actual departure to configured checkout provider custom
purchase_pro Server-verified successful purchase, with provider event ID and paid amount order_created / contents

download_lite proves an archive response reached the browser, not that a user saved, installed or used it. Native modified-click downloads can bypass the JavaScript measurement. Record that measurement limit instead of manufacturing an event.

The prompt's labels lead, complete_registration and purchase were conceptual examples. The current documented API names are lead_created, registration_completed and order_created. Use registration_completed only for an actual completed registration; a free download is not registration. A clicked payment link alone is not evidence of checkout_started. Official supported events.

Custom events can support internal reporting, but are not currently supported as the selected objective of a conversion-optimized campaign. Begin with CPC validation until a supported standard event actually works and is selected in the account. Conversion-optimized campaigns.

Configuration and adapter boundaries

Public browser build configuration: OPENAI_ADS_PIXEL_ID, OPENAI_ADS_CONVERSION_SOURCE_ID, OPENAI_ADS_ENABLED=false, POSTHOG_KEY, GA_MEASUREMENT_ID, TRACKING_MODE=off. Public IDs alone do not install an SDK or make tracking work.

OPENAI_ADS_CONVERSIONS_API_KEY belongs exclusively in a server environment such as tracking/server/.env; it must never appear in a generated static file. config.mjs serializes an explicit public allowlist. The Node server placeholder is outside the static build and deliberately sends no network requests.

Use one event ID for the same browser and server conversion, and amount in integer currency minor units: €49 = 4900 with EUR. Persist deduplication server-side; browser session state alone is insufficient. Do not send a default price instead of the amount actually paid. Conversion measurement.

Thank-you states

/thank-you/?state= supports waitlist-submitted, free-lite-downloaded, pro-checkout-clicked, and purchase-completed. The URL chooses explanatory content, never establishes payment or signup truth. Local download/checkout receipts can clarify the preceding action; reloading the page does not emit it again. Direct visits and forged query parameters produce no lead or purchase.

A real waitlist or purchase integration must verify its outcome with the server/provider and then invoke trackEvent with a stable provider-derived eventId and confirmationSource: 'verified_server_response'. That marker is a caller contract, not a security check: the server must independently verify the event before sending Conversions API data. No unauthenticated browser endpoint accepts purchase claims in this repository.

Attribution across checkout

Outgoing configured checkout URLs retain oppref. This only verifies the outgoing URL. A provider may ignore arbitrary query parameters; configure documented custom metadata or a server-created attribution record and correlate the verified webhook with the initial click. Do not assume that an appended query parameter automatically appears in a receipt/webhook. See marketing/sales/checkout-options.md for provider choices and the test procedure.

Preserve click references in allowed return URLs and pass the verified server-side attribution in the conversion envelope. Restrict outgoing propagation to the selected providers; privacy/support/documentation sites do not need it. The same-origin page path is tracked without arbitrary query content. Set a restrictive referrer policy on the public deployment to prevent query leakage to unrelated origins.

Verification before paid traffic

Run the tracking unit tests and browser journey with a synthetic oppref. Inspect each internal link, the configured checkout URL and the appropriate thank-you state. Confirm there is no secret in the static build, no external measurement request before consent, no fake event after a failed ZIP response, and no purchase from visiting a URL. Then repeat with the actual Pixel/account, chosen provider and verified conversion. Local adapter tests do not substitute for this final account-level validation.