Customers running paid campaigns (e.g. Yuma's LinkedIn ads) lose attribution before the form is ever opened: the Surface tag reads window.location.search fresh on every STORE_UPDATE, so when a visitor lands on a paid landing page with utm_source/utm_medium/click-id params and then navigates to another page (often via an internal CTA that replaces the query string with internal tags like utm_content=home_homepage-hero), the paid params never reach URL_DATA.params and the HubSpot UTM hidden fields. Verified in Yuma's prod data: across two LinkedIn campaign windows, zero genuine prospect submissions carried LinkedIn-paid params — only internal test submissions that converted directly on the tagged landing page. Result: Paid Social shows zero in the customer's HubSpot; contacts land as Direct/Offline.
Tag-only change in trysurface/scripts (no surface_forms changes needed):
src/store/first-touch.ts: on page load and route change, if the URL carries qualifying attribution params, persist { params, url, referrer, at } in a first-party cookie surface_first_touch (30-day max-age, base-domain scoped via getJourneyCookieDomain(), values capped at 256 chars).utm_source, utm_medium, or a click-id (gclid, fbclid, li_fat_id, msclkid, ttclid). utm_content alone does NOT qualify (internal CTA tags would poison the slot). Skip capture when document.referrer is same-origin (internal navigation). Write-once until expiry (strict first-touch, matching HubSpot Original Source semantics).SurfaceStore.getPayload(): urlParams: { ...getFirstTouchParams(), ...this.urlParams } — current-page params win, first-touch fills gaps. Merging in getPayload() (not sendPayloadToIframes()) also covers the direct notifyIframe() call on the embed partial-fill path.firstTouchParams postMessage field; surface_tracking.js.test/first-touch.html) — tagged landing → navigate → verify merged STORE_UPDATE; internal-tag + same-origin-referrer guards; write-once. Rollout: pnpm run build (committed artifact) + jsDelivr @latest cache purge.