> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Track UTM Campaigns & Attribution

> Tag your marketing links to measure which campaigns drive signups, sales, and revenue

When a visitor lands on your site, you can't ask them how they got there. UTMs answer that for you — small URL parameters that tell your analytics tool the exact source of every visit. Done right, you can measure which LinkedIn post drove revenue, which email variant converted, which affiliate actually worked.

## The 5 UTM Parameters

* `utm_source` — **where** the click came from (google, linkedin, newsletter, affiliate\_name)
* `utm_medium` — **how** the click happened (cpc, email, social, referral)
* `utm_campaign` — **which campaign** it belongs to (spring\_sale, launch\_2026, retarget\_abandoners)
* `utm_content` — **which variant** of the ad/link (sidebar, cta\_button, hero\_v2)
* `utm_term` — **which keyword** (for paid search only)

## Example Tagged URL

```
https://yoursite.com/signup?utm_source=linkedin&utm_medium=social&utm_campaign=april_launch&utm_content=post_1
```

This one URL tells you: someone clicked a LinkedIn social post, specifically the first variant, as part of the April launch campaign.

## Three Paths

<Tabs>
  <Tab title="AI Chat (easiest)">
    Ask the AI:

    ```
    Generate UTM-tagged URLs for my LinkedIn, Twitter, and 
    email campaigns for our April product launch. Target URL 
    is yoursite.com/signup.
    ```

    The AI generates the URL variants and exports them as a CSV or ready-to-paste list for your marketing tools.
  </Tab>

  <Tab title="UTM builder tool">
    Use a free URL builder like [ga-dev-tools.google/campaign-url-builder](https://ga-dev-tools.google/campaign-url-builder/). Fill in the fields, copy the tagged URL.
  </Tab>

  <Tab title="Manual">
    Just add the params yourself. Keep a shared spreadsheet so your team uses consistent naming.
  </Tab>
</Tabs>

## Capture UTMs on Your Site

Reading UTMs is easy. Persisting them across pages and attaching them to conversions is where most sites go wrong.

<Steps>
  <Step title="Read from the URL on first load">
    ```typescript theme={null}
    const params = new URLSearchParams(window.location.search);
    const utms = {
      source: params.get('utm_source'),
      medium: params.get('utm_medium'),
      campaign: params.get('utm_campaign'),
      content: params.get('utm_content'),
      term: params.get('utm_term'),
    };
    ```
  </Step>

  <Step title="Save to localStorage or a cookie">
    ```typescript theme={null}
    if (utms.source) {
      localStorage.setItem('utms', JSON.stringify(utms));
      localStorage.setItem('utms_first_touch', JSON.stringify(utms));
    }
    ```

    Use `utms_first_touch` only if it's not already set, so first-touch attribution doesn't overwrite on later visits.
  </Step>

  <Step title="Attach to signup/form submit">
    When a user submits a signup form, include the stored UTMs in the payload so they end up on the user record.
  </Step>

  <Step title="Persist across pages">
    Storing in localStorage or cookies automatically survives page navigation. If you set them via a third-party script, make sure scripts run on every page that might capture a signup.
  </Step>
</Steps>

## Database Schema

Add UTM columns to your `users` or `signups` table:

```sql theme={null}
ALTER TABLE users ADD COLUMN utm_source text;
ALTER TABLE users ADD COLUMN utm_medium text;
ALTER TABLE users ADD COLUMN utm_campaign text;
ALTER TABLE users ADD COLUMN utm_content text;
ALTER TABLE users ADD COLUMN utm_term text;
ALTER TABLE users ADD COLUMN first_touch_campaign text;
```

Store both the most recent touch (at signup) and the first touch (earliest captured).

## Analytics Integration

* **Google Analytics** picks up UTMs automatically. You'll see Campaign, Source, Medium in the Acquisition reports out of the box. See [Add Google Analytics](/how-tos/add-google-analytics).
* **Hiveku visitor analytics** captures referrer by default; adding UTM capture lets you attribute beyond raw referrer.
* Event-based tools like Segment, Mixpanel, or PostHog: forward the UTMs as properties on every event for proper funnel breakdowns.

## Attribution Models

There's no single "right" answer — each model tells a different story:

| Model       | Credit goes to                 | Best for                               |
| ----------- | ------------------------------ | -------------------------------------- |
| First-touch | The first UTM a user saw       | Top-of-funnel / awareness campaigns    |
| Last-touch  | The last UTM before conversion | Bottom-of-funnel / closing campaigns   |
| Multi-touch | Split across all touches       | Longer sales cycles, multiple channels |

Most teams use both first-touch and last-touch side-by-side — awareness and conversion credit serve different marketing decisions.

## Build an Attribution Dashboard

Query your users table:

```sql theme={null}
SELECT 
  utm_source, utm_medium, utm_campaign,
  COUNT(*) as signups,
  COUNT(CASE WHEN converted = true THEN 1 END) as paid,
  SUM(CASE WHEN converted THEN ltv_cents ELSE 0 END) / 100.0 as revenue
FROM users
WHERE created_at >= now() - interval '30 days'
GROUP BY utm_source, utm_medium, utm_campaign
ORDER BY revenue DESC;
```

Plug this into your admin dashboard so the marketing team can see per-campaign performance without asking you.

## Naming Conventions

Consistency matters more than cleverness:

* **Lowercase only.** `LinkedIn` and `linkedin` are different values in analytics.
* **No spaces.** Use `-` or `_` (pick one and stick with it): `email-newsletter` or `email_newsletter`.
* **Abbreviate predictably.** `cpc` not `click_per_cost`; `oct_launch` not `october_product_launch`.
* **Standardize values.** Don't use `social`, `Social`, and `social-media` for the same medium.

<Tip>
  Create a shared UTM naming spreadsheet for your team. Inconsistent capitalization or typos fragment your data and make reporting useless. A 30-minute alignment meeting upfront saves weeks of cleanup later.
</Tip>

## Verify It Worked

<Steps>
  <Step title="Tag a test URL">
    ```
    https://yoursite.com?utm_source=test&utm_medium=qa&utm_campaign=verify
    ```
  </Step>

  <Step title="Visit it in an incognito window">
    Incognito avoids picking up your existing session's UTMs.
  </Step>

  <Step title="Open browser devtools">
    Check localStorage: you should see `utms` and `utms_first_touch` keys.
  </Step>

  <Step title="Submit a signup form">
    Complete a test signup. Then query your DB: the new user row should have `utm_source = 'test'`, `utm_campaign = 'verify'`, etc.
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="UTMs not capturing">
    Most common cause: JavaScript error on the page blocking the capture script. Open devtools console and look for red errors. Also check cookies aren't disabled (try in normal browsing mode, not incognito with strict settings).
  </Accordion>

  <Accordion title="Data fragmented across cases (LinkedIn, linkedin, Linked-in)">
    Standardize going forward and clean up historical data with a SQL UPDATE:

    ```sql theme={null}
    UPDATE users 
    SET utm_source = LOWER(REPLACE(utm_source, ' ', '-'))
    WHERE utm_source IS NOT NULL;
    ```

    Add a constraint or transformation at write time so it can't happen again.
  </Accordion>

  <Accordion title="Cross-domain sessions lose UTMs">
    If visitors bounce between yoursite.com and app.yoursite.com, cookies may not transfer. In GA4, enable cross-domain tracking in Admin > Data Streams. For your own capture logic, use a cookie scoped to the top-level domain (`.yoursite.com`).
  </Accordion>

  <Accordion title="Organic vs paid search mis-attributed">
    Google auto-tags paid search as `google / cpc` via Google Ads' auto-tagging feature (gclid parameter). Don't manually add UTMs on top of this — you'll double-count. Respect the auto-tagging and only use manual UTMs where the source doesn't provide its own attribution.
  </Accordion>

  <Accordion title="Revenue doesn't match payment processor">
    Attribution captures revenue at signup; refunds and chargebacks happen later. Build your attribution report off net revenue (post-refund) for accuracy. Update the stored `ltv_cents` monthly from Stripe data.
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Analytics" icon="chart-line" href="/how-tos/track-analytics">
    Track every important action on your site
  </Card>

  <Card title="Google Analytics" icon="google" href="/how-tos/add-google-analytics">
    Connect GA4 for deeper attribution reports
  </Card>
</CardGroup>
