AI Global Academy Join the waitlist

Courses / Analytics and measurement

Lesson 6.8 · 60 minAttribution: sources into the CRM

Duration~60 min in the lesson + ~35 min homework
PrerequisitesCheckpoint lesson-6.6; the dashboard from 5.6
Checkpointlesson-6.8

What you will have

Every new lead carries its source: UTM values, referrer and landing page, plus the ad click identifier where the visitor allowed advertising. The source shows on the lead card, and the dashboard has a "leads by source" chart. What is stored in the browser follows the consent choice from 6.5.

Video

The video for this lesson is not recorded yet.

Prompts used in this lesson

Part 4 — Claude implements capture and storage

Prompt to Claude
Purpose: every lead in the CRM must show where it came from, without storing
anything in a visitor's browser that they did not agree to.

Context: the consent module can answer "is analytics allowed?" and "is advertising
allowed?" and announces changes. The lead form posts to /api/leads. The leads table
is meant to have the columns utm_source, utm_medium, utm_campaign, utm_content,
utm_term, gclid, fbclid, referrer, landing_page.

Done looks like:
1. A small attribution module. On the first public page load of a visit it reads the
   five utm_ parameters, gclid and fbclid from the address; the referrer, only if it
   is another website; the landing page as a path without its query string.
2. Consent rules, exactly:
   - No choice or rejected: write nothing to cookies, localStorage or sessionStorage.
     Hold UTM values, referrer and landing page in memory only. Discard gclid/fbclid.
   - Analytics allowed: store UTM values, referrer and landing page as first-party
     data, first touch (never overwrite a stored source), with the date; ignore
     stored values older than 90 days.
   - Advertising allowed: store gclid and fbclid as well.
   - On a consent change apply the new rules at once; on withdrawal delete what is
     no longer allowed.
3. The form sends what the rules allow. /api/leads validates each field (type,
   maximum length, lowercase utm_ values), stores them in the matching columns and
   null for anything absent. A submission with no attribution still succeeds.
4. If a column is missing, add a migration in supabase/migrations/ and tell me how
   to apply it. Otherwise change nothing in the database.
5. The cookies section on /privacy says what is stored for attribution.

Constraints:
- Attribution values never go to the data layer or any analytics tool.
- Never store or send the landing address with its query string.
- Spam protection, validation and notifications of /api/leads keep working;
  existing tests pass. Add tests for the three consent cases.

Verify before reporting: run the build and tests. Then open the site in a browser
with ?utm_source=test&utm_medium=check&utm_campaign=verify&gclid=TESTCLICK and report
cookies, localStorage and sessionStorage (a) before a choice, (b) after "Reject
all", (c) after analytics only, (d) after accepting all. Submit one lead in case (d)
and show me the stored row's attribution columns.

Part 5 — Lead card and dashboard

Prompt to Claude
Purpose: I want to see each lead's source in the CRM and compare sources on the
dashboard.

Context: /admin/leads/<id> is the lead card; the dashboard is the admin home page
with a period selector and charts. New leads now have attribution columns filled;
older leads have nulls.

Done looks like:
1. Lead card: a read-only "Source" block with source and medium, campaign, content,
   term, landing page, referrer, and whether a Google or Meta click ID is present
   (that it exists, not the full value).
2. One display rule in one function, used everywhere: the source label is utm_source
   if present; otherwise the referrer's domain; otherwise "direct / unknown".
3. Lead list: a Source column with that label.
4. Dashboard: a "Leads by source" chart for the selected period, in the style of the
   existing charts, with the count per source visible.

Constraints: follow docs/design.md and the existing admin components. Do not change
how other dashboard figures are calculated.

Verify before reporting: run the build and tests; load the dashboard and a lead
card; compare the chart's counts with a direct database count for the same period
and report both.

Do along

Work on your own project. Pause the video where a step says so.

  1. Pause after Part 1. Write your UTM convention and first campaign names into docs/measurement-plan.md.
  2. Pause when the prompt appears in Part 4. Run it. Keep the consent rules as written unless you have advice to store less. Check the four-row storage report against the table in Part 3. Apply the migration if one was created.
  3. Pause when the prompt appears in Part 5. Run it. Compare chart counts with the database counts.
  4. Commit, push, deploy.
  5. Pause after Part 6. Build three test links for your domain with three different sources. Open each in a new private window, make a different consent choice in each, and submit the form.
  6. Check the three leads and the chart, then delete the test leads.
  7. Ask Claude to add an "Attribution" section to the measurement plan: what is captured, the consent table, first touch, the 90-day limit, the display rule. Commit and tag.

Check your work

  1. Open the lead list. Expected: the three test leads show three different sources.
  2. Open the "accept all" lead. Expected: source, medium, campaign filled; click ID shown as present.
  3. Open the "analytics only" and "reject all" leads. Expected: source filled; no click ID.
  4. In the private window where you rejected, open DevTools → Application. Expected: no UTM values or click IDs in cookies, local storage or session storage.
  5. Open the dashboard for today. Expected: "Leads by source" shows your three sources with one lead each.

Common problems

  • All three leads show the same source. → You reused one browser window, so the first-touch value was kept. → Use a new private window per link.
  • Source is empty for the "reject all" lead. → The page was reloaded before submitting, so the in-memory values were lost. → Submit without reloading. If still empty, tell Claude which rule is not being followed.
  • A click ID was saved for a visitor who did not allow advertising. → A consent rule is wrong, or the address was stored whole. → Treat it as a defect: give Claude the row, have it fix, add a test and verify again.

Homework

About 35 minutes, after the lesson. Nothing here is needed to start 6.9. Keep the UTM convention and change no code. Write in your course notes, and delete test leads when you finish.

  1. Label the links you already have (~15 min). List every place of your own that links to your site today: a business profile, social bios, your email signature. For each, build a link with source, medium and campaign following the convention, and replace the old link wherever that costs nothing. Done when: every place has a row with three lowercase values, and each new link opens your site.
  2. Three cases the lesson did not test (~15 min). Each in a new private window, submitting with a "QA" name: (a) type your address with no labels; (b) arrive by clicking an unlabelled link on another website, such as your social profile; (c) open one labelled link and accept all, then open a second with a different source in the same window, and submit. Write the source each lead shows. Done when: (a) shows "direct / unknown", (c) shows the first link's source, and you can explain (b) with the display rule from Part 5.
  3. Sources you remember (~5 min). Older real leads show "direct / unknown". Where you know how the person found you, add a note saying so on the lead card. Done when: every real lead whose origin you know has that note.

Save your work

git add -A
git commit -m "Lesson 6.8: lead attribution with consent-aware storage"
git tag lesson-6.8
git push
git push --tags