# Campaign attribution troubleshooting - Docs

This page covers troubleshooting for campaign attribution issues. For general web analytics troubleshooting, see the [main troubleshooting page](/docs/web-analytics/troubleshooting.md). If you're using [Marketing Analytics](/docs/web-analytics/marketing-analytics.md) to tie ad spend to conversions, the same attribution mechanics apply – campaigns are matched to your conversion events via `utm_campaign` and `utm_source`.

## Have a question? Ask PostHog AI

Ask PostHog AI

## Why are my UTMs showing on pageviews but not on conversions?

If you see UTM parameters on `$pageview` events but conversions aren't attributed correctly, the issue is almost always **identity fragmentation** – not a UTM capture problem.

For campaign attribution to work, the user's [person profile](/docs/data/persons.md) must stay connected from landing to conversion. A person profile is PostHog's representation of a user, identified by a `distinct_id`. Here's the typical flow:

1.  User lands with UTMs
2.  PostHog captures UTMs on `$pageview`
3.  A `distinct_id` is generated
4.  User converts (e.g., `signed_up`)
5.  `identify()` is called
6.  All prior events with that same `distinct_id` get stitched to the person

If the `distinct_id` changes at any point before conversion, attribution breaks.

### Why does the `distinct_id` change?

The `distinct_id` can change when:

-   **Storage is cleared or isolated** – Browsers like Safari aggressively clear cookies and localStorage, especially for third-party contexts
-   **Users switch browsers or devices** – Each browser/device gets a new anonymous `distinct_id`
-   **In-app browsers are involved** – Apps like LinkedIn, Instagram, and Facebook use embedded webviews with isolated storage
-   **Persistence is set to memory** – Without persistent storage, every page load creates a new identity
-   **PostHog reinitializes** – Multi-app architectures or SSR apps may create duplicate PostHog instances

When any of these happen before `identify()` is called, the original `distinct_id` (with the UTM data) becomes orphaned from the conversion event.

### How do I check if this is happening?

Run this SQL query to compare landing and conversion events:

SQL

[Run in PostHog](https://us.posthog.com/sql?open_query=select%0A++++event%2C%0A++++properties.utm_source%2C%0A++++properties.utm_campaign%2C%0A++++distinct_id%2C%0A++++person_id%0Afrom+events%0Awhere+timestamp+%3E%3D+today%28%29+-+7%0Aand+properties.utm_source+%3D+'linkedin'+--+replace+with+your+utm_source%0Aorder+by+timestamp+desc%0Alimit+100)

PostHog AI

```sql
select
    event,
    properties.utm_source,
    properties.utm_campaign,
    distinct_id,
    person_id
from events
where timestamp >= today() - 7
and properties.utm_source = 'linkedin' -- replace with your utm_source
order by timestamp desc
limit 100
```

If you see different `distinct_id` or `person_id` values for pageviews and conversions from the same user, you have identity fragmentation.

## Why are users from LinkedIn/Meta ads not being attributed?

This is a common issue when running ads on social media platforms that open links in their native in-app browsers. Here's a typical scenario:

1.  A user sees your ad in the LinkedIn mobile app
2.  They tap the link, which opens in LinkedIn's in-app browser (webview)
3.  PostHog captures the `$pageview` with UTM parameters and assigns a `distinct_id`
4.  The user decides to sign up but prefers using their main browser
5.  They tap "Open in Safari" or navigate to your site directly
6.  A new `distinct_id` is created because Safari doesn't share storage with LinkedIn's webview
7.  The conversion event is now tied to a different person profile than the original pageview

In-app browsers (LinkedIn webview, Meta webview, Instagram) cause attribution issues because:

-   Storage is isolated from the system browser
-   Cookies aren't shared when users switch to their main browser
-   The `distinct_id` resets on navigation
-   App-to-browser handoffs reset storage

You can detect this by checking user agents containing `linkedin`, `fbav`, or `instagram`. If a large share of your traffic comes from these sources, expect higher identity fragmentation.

The best fix depends on your specific situation, but it usually involves propagating UTMs or the `distinct_id`/session ID as much as possible:

1.  Pass UTM parameters through your signup flow (e.g., in hidden form fields or URL parameters)
2.  Identify users as early as possible in the funnel
3.  Use [cross-domain tracking](/tutorials/cross-domain-tracking.md) if you have separate marketing and app domains

If UTMs and identity look fine but campaigns still don't match in Marketing Analytics, the problem may be a naming mismatch instead: your `utm_campaign` or `utm_source` values don't match what the ad platform reports. Fix this with [UTM campaign manual mappings](/docs/web-analytics/marketing-analytics.md#utm-campaign-manual-mappings) and [custom source mappings](/docs/web-analytics/marketing-analytics.md#custom-source-mappings) – for example, mapping `utm_campaign=summer-promo` to the campaign named "Summer Sale 2025", or attributing `utm_source=fb` to Meta Ads.

## Why does attribution break when users navigate between pages?

This typically happens when the storage is disconnected on navigation:

**1\. Persistence is set to memory**

If `persistence: "memory"` is configured (common when cookie consent isn't granted), each page load creates a new `distinct_id`. Check your config:

JavaScript

PostHog AI

```javascript
posthog.init("key", {
  persistence: "localStorage+cookie" // Recommended
})
```

**2\. PostHog is reset**

PostHog may reinitialize without preserving the `distinct_id` if:

-   Your landing page and signup page reload the entire app
-   You're using SSR apps or multi-app architectures
-   You have separate marketing and app codebases
-   You're using two different PostHog instances (e.g., different project tokens for marketing vs app)

Ensure PostHog initializes once and the `distinct_id` persists across pages.

**3\. Subdomain mismatch**

If your marketing site is at `marketing.example.com` and your app is at `app.example.com`, cookies must be configured at the root domain level. See [cross-domain tracking](/tutorials/cross-domain-tracking.md) for setup instructions.

## Why do ad platform clicks not match my attributed conversions?

Ad platforms often count interactions that aren't website visits:

-   Likes, shares, and comments
-   Profile clicks
-   Video views
-   Carousel swipes

Not all "clicks" result in a pageview. Expect discrepancies between platform metrics and UTM-based attribution.

Ad platforms also attribute conversions using their own pixel data and attribution windows (e.g., 7-day click, 1-day view), which differ from PostHog's event-based attribution. This is why Marketing Analytics shows both **reported conversions** (from the ad platform) and **PostHog conversion goals** side by side – see [reported vs PostHog conversions](/docs/web-analytics/marketing-analytics-schema.md#understanding-reported-vs-posthog-conversions) for a full explanation.

## Why is mobile attribution worse than desktop?

Modern iOS and mobile browsers have privacy restrictions that affect tracking:

-   Restricted cookie lifetime
-   Isolated webview storage
-   Session storage resets
-   Intelligent Tracking Prevention (ITP)

This disproportionately impacts mobile campaign attribution. Cross-device attribution also fails when:

1.  User clicks ad on mobile
2.  Browses anonymously
3.  Signs up later on desktop

Unless you explicitly connect those identities with `identify()`, the mobile pageviews remain separate from the desktop conversion.

## How do I debug identity fragmentation?

Run this query to find cases where the same device has multiple persons:

SQL

[Run in PostHog](https://us.posthog.com/sql?open_query=select%0A++++properties.%24ip%2C%0A++++properties.%24raw_user_agent%2C%0A++++uniq%28person_id%29+as+persons%2C%0A++++uniq%28distinct_id%29+as+distinct_ids%0Afrom+events%0Awhere+timestamp+%3E%3D+today%28%29+-+30%0Aand+properties.utm_source+%3D+'linkedin'+--+replace+with+your+utm_source%0Agroup+by+properties.%24ip%2C+properties.%24raw_user_agent%0Ahaving+persons+%3E+1%0Alimit+100)

PostHog AI

```sql
select
    properties.$ip,
    properties.$raw_user_agent,
    uniq(person_id) as persons,
    uniq(distinct_id) as distinct_ids
from events
where timestamp >= today() - 30
and properties.utm_source = 'linkedin' -- replace with your utm_source
group by properties.$ip, properties.$raw_user_agent
having persons > 1
limit 100
```

If `persons > 1` for the same IP and user agent, you have likely found identity fragmentation. Note that IP and user agent matching is not a guaranteed fingerprint – multiple users can share the same IP (e.g., office networks, VPNs) and user agent combination. However, it's a useful approximation for detecting fragmentation patterns.

You can also compare conversion IPs to ad click IPs. If you see conversions from the same IP/user agent as your ad clicks, the traffic is arriving – it's just not being stitched to the right person.

## How do I fix attribution issues?

**1\. Ensure persistence is not memory**

JavaScript

PostHog AI

```javascript
posthog.init("key", {
  persistence: "localStorage+cookie"
})
```

**2\. Identify users early**

Call `identify()` as early as possible in the funnel to reduce anonymous navigation.

**3\. Bootstrap across domains**

If you have multiple domains or apps, pass `distinct_id` and `session_id` explicitly. See [cross-domain tracking](/tutorials/cross-domain-tracking.md).

**4\. Minimize page reloads**

Keep users in a single-page flow before conversion when possible.

## When should I raise a support ticket?

Raise a support ticket if:

-   UTMs are present on pageviews
-   `distinct_id` changes unexpectedly
-   Identity stitching appears broken despite correct persistence config
-   In-app browser flows consistently fragment identity
-   Subdomain configuration is correct but still failing

Include:

-   Example `distinct_id`(s) and `person_id`(s)
-   User agent
-   Campaign name
-   Your persistence config
-   Your domain structure

[Raise a support ticket here](https://app.posthog.com/home#supportModal).

## Solved community questions

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better