Status (2026-07-21): Phases 0–2, 4, 5 shipped and deployed. Consent Mode v2, the capture module, journey attachment to every lead form, the workspace Attribution dashboard, and GA4
generate_leadconversion events are all built (unit + browser-E2E verified) and live. Only Phase 3 (Salesforce write-back) remains — blocked on the SF custom fields. Console follow-ups: mark the GA4 conversion, LinkedIn Insight Tag/retargeting (WEB-86), optional Looker board. Full design:docs/superpowers/specs/2026-07-20-lead-attribution-design.md; plans underdocs/superpowers/plans/2026-07-21-*.
The team needs to answer, for every single lead: where did it come from, which ad/campaign produced it, and what path did the person walk (which blog posts, which pages, over how many sessions) before converting. This is a per-lead attribution capability for the whole GTM/marketing/sales team — not a single report.
GA4 answers aggregate/session questions but cannot join "this specific person who booked a demo" to "the Groww LinkedIn ad + these 3 blog posts." That join is what this system builds.
GTM-5WWQQTPD, injected in production from app/layout.tsx
(skips /workspace). Consent is now handled by Google Consent Mode v2
(components/analytics/ConsentModeInit.tsx + ConsentUpdateBridge.tsx, Phase 0 shipped):
analytics defaults granted (essential), ad signals stay denied until marketing consent. The old
unwired AnalyticsGuard.tsx has been removed.lib/datasheet-tracking.ts;
webinar forms capture some; the demo form forwards only a raw query string.lead_source only./blogs/[slug] with no lead form or attribution attached.The full multi-touch journey lives client-side in a durable first-party cookie plus a capped,
schema-versioned localStorage timeline. The entire journey is transmitted only when a lead
form is submitted, at which point it is joined to the now-known lead. Anonymous journey capture
is always-on (functional); the join to PII and all third-party pixels are consent-gated.
Non-converter volume (denominators for conversion rates) comes from GA4 aggregate — no custom
PII event stream is needed.
Any page (incl. /blogs/[slug])
└─ lib/attribution client module (planned)
├─ ensures durable first-party visitor cookie (mai_vid, ~2yr)
├─ captures per-session first-touch + last-touch (utm×5, gclid, fbclid, li_fat_id, referrer, landing)
└─ appends a capped, ordered pageview to the localStorage journey
│ (on ANY lead form submit)
▼
Form → existing API route (demo / datasheets / whitepapers / webinars / contact)
├─ Mongo lead record ← FULL journey blob (unified shape)
├─ Salesforce Web-to-Lead ← COMPACT summary via hidden inputs
└─ Slack (demo only, unchanged)
│
apps/workspace ── Marketing Attribution dashboard (reads Mongo)
GA4 / Looker ── aggregate funnels + denominators + LinkedIn retargeting
LeadJourney model (planned — types/attribution.ts)#A single schema-versioned shape shared by the client, every API route, MongoDB, and the
dashboard: visitorId, firstTouch, lastTouch, sessionCount, and a capped ordered
pageviews[] (each carrying path, sessionId, contentType, and slug — the slug is what
powers "which blog post generated the lead"). Truncation (when a journey exceeds the cap) is
recorded explicitly so the dashboard never implies a complete path when it isn't.
ConsentManager grant, enforced with Google Consent Mode v2.
This work also fixes the currently-dead AnalyticsGuard wiring.PENDING_TASKS.md).Every paid or campaign link must carry UTMs, or the click is unattributable (this is the direct cause of the "52 LinkedIn clicks, 0 in GA" gap). Standard:
| Param | Meaning | Rule / examples |
|---|---|---|
utm_source | platform | lowercase — linkedin, google, newsletter |
utm_medium | channel type | paid_social, cpc, email, organic_social, referral |
utm_campaign | campaign | {topic}-{yyyymm} — e.g. groww-casestudy-202607 |
utm_content | creative / variant | pinned-comment, hero-video, carousel-a |
utm_term | keyword | paid search only |
Rules: lowercase, hyphen-separated, no spaces. Put the UTM on the destination link in the post or pinned comment — not on the image (image taps don't carry it). Verify a test click in GA4 DebugView / Realtime; standard reports lag 24–48h.
Example:
https://matters.ai/case-studies/credit-saison?utm_source=linkedin&utm_medium=paid_social&utm_campaign=groww-casestudy-202607&utm_content=pinned-comment
Added inside the GTM container GTM-5WWQQTPD (not in code), fired on all pages via a trigger
gated on marketing consent (Consent Mode ad_storage = granted). It builds the retargeting
audience and confirms LinkedIn traffic. Capturing li_fat_id from the URL into the lead journey
lands in Phase 1. Google Consent Mode v2 defaults are set in code before GTM
(components/analytics/ConsentModeInit.tsx) and updated from the cookie banner via
components/analytics/ConsentUpdateBridge.tsx.
A compact summary (first/last source·medium·campaign, landing page, click id, session count, and
Attribution_Visitor_Id__c as the join key) is pushed via hidden Web-to-Lead inputs. This
requires ~10 custom Lead fields created by a Salesforce admin — field spec in PENDING_TASKS.md.
Web-to-Lead is fire-and-forget, so MongoDB is the source of truth and SF is best-effort. A
follow-up ticket upgrades this to full bidirectional JWT REST sync (reusing the partners-portal
pattern).
apps/workspace — shipped)#A team-wide module (org:marketing:view RBAC), themed through @matters/theme, at
/marketing-attribution ("Lead Attribution" in the sidebar + Command Center). Reads the four
wired lead collections (demo, datasheet, whitepaper, webinar registrations) and shows: total /
attributed / attribution-rate / top-source tiles, a first-touch source donut, campaigns
bar, blog → lead bar (which posts precede conversions), and a recent-lead feed with each
lead's first source, campaign, session + pageview counts, and blogs read. It also has a
date-range filter (7/30/90/all), an attributed-leads-over-time trend, a first-touch vs
last-touch source pair (how they found us vs what converted them), and each recent-lead row
expands to its ordered journey path
(e.g. linkedin → /blogs/credit-saison → /blogs/groww → /demo). An Export CSV button
downloads the attributed leads for the selected range (attribution columns + masked email;
raw-lead export stays in the Sales module). It further surfaces funnel stats (avg pages/lead,
avg sessions/lead, avg time-to-convert, multi-session rate), a content-type mix, lead-type
mix, channel/medium, top landing pages, and a pages-before-converting distribution.
Aggregation:
lib/services/marketing/attribution-aggregate.ts (pure, unit-tested) +
attribution.service.ts (Mongo reads). GA4-supplied denominators come with Phase 5.
ConsentModeInit + ConsentUpdateBridge), dead
AnalyticsGuard removed, UTM naming convention. (LinkedIn Insight Tag itself is a GTM-console
task — WEB-86.)lib/attribution capture module (parse/journey/collect) +
types/attribution.ts LeadJourney + JourneyRecorder. Unit-tested + browser-verified.apps/workspace → Lead Attribution
(/marketing-attribution, org:marketing:view): source/campaign donut+bars, blog → lead
attribution, and a recent-lead feed with per-lead journey summary. Reads the four wired lead
collections via lib/services/marketing/attribution.service.ts.generate_lead event on success
(lib/analytics.ts TRACKING_CONFIG + LEAD_GEN_FORMS; wired into demo, datasheet,
whitepaper, and webinar forms), which GTM can forward to the LinkedIn / Google Ads conversion
tags. Remaining = console: mark generate_lead as a Key Event/conversion in GA4, stand up the
LinkedIn retargeting audience (WEB-86), and (optional) a Looker Studio board.mattersai-datasheets.datasheet_leads / mattersai-whitepapers.whitepaper_leads and $incs a
download_count on the content doc. Surfaced in workspace → Datasheets / Whitepapers →
Analytics (Total Downloads, Avg, Top Performers), fed by
apps/workspace/app/api/{datasheets,whitepapers}/admin/stats/route.ts. These leads also carry
leadJourney, so they appear in the Lead Attribution dashboard too.read_what_matters___case_studies), rendered
via BlogPostTemplate (isCaseStudy) at the catch-all /[...slug]. The PDF (acf.pdf_download_url)
is now gated: the Download button opens a popover (components/case-studies/CaseStudyDownloadGate.tsx)
with a short lead form; on submit it records a lead (name/email/company + attribution
leadJourney) via POST /api/case-studies/download → mattersai-leads.case_study_leads, fires
the GA4 + dataLayer case_study_download event, then opens the PDF. Cancel just closes — no
permanent UI change on the page. So case-study downloads now capture who (a real lead,
visible in Lead Attribution since case_study_leads is a wired collection) and how many
(GA4 + lead count). Case-study page views remain GA4 pageviews (via GTM GTM-5WWQQTPD).Geo / B2B-data-provider coverage (Indian/UAE leads), full bidirectional Salesforce REST sync (future ticket), and reverse-IP company deanonymization of non-converters.