---
title: Revenue Attribution
description: Track revenue from Stripe and attribute it to traffic sources
url: https://www.supalytics.co/docs/revenue-attribution
---



Revenue attribution connects your Stripe payments to visitor traffic sources, letting you see which channels drive actual revenue—not just visits.

**Using Paddle, LemonSqueezy, or another provider?** See the [Revenue Attribution API](/docs/api/revenue-attribution) instead.

## How It Works

1. Supalytics tracker assigns a `visitorId` to each visitor
2. You capture and store this `visitorId` (timing depends on your tracking mode)
3. You pass the stored `visitorId` to Stripe when creating a payment
4. Supalytics syncs Stripe charges and matches them with pageview data
5. Revenue appears in your dashboard, attributed to the visitor's traffic source

## Setup

### 1. Connect Stripe

Go to **Settings → Integrations** and connect your Stripe account with a restricted API key.

Create a restricted key in [Stripe Dashboard](https://dashboard.stripe.com/apikeys/create?name=Supalytics\&permissions%5B%5D=rak_charge_read\&permissions%5B%5D=rak_invoice_read\&permissions%5B%5D=rak_subscription_read) with **Charges: Read**, **Invoices: Read**, and **Subscriptions: Read** permissions.

### 2. Capture the Visitor ID

The approach depends on your [tracking mode](/docs/tracking-modes):

| Tracking Mode | When to Capture       | Why                                    |
| ------------- | --------------------- | -------------------------------------- |
| Privacy Mode  | At signup             | Visitor IDs rotate daily—capture early |
| Cookie Mode   | At signup OR checkout | Visitor IDs persist—either works       |

***

## Privacy Mode: Capture at Signup

In privacy mode, visitor IDs rotate daily. If a user visits Monday, signs up Tuesday, and pays Friday—each day has a different ID. **You must capture at signup** to maintain attribution.

### Client-side: Send visitor ID with signup

```javascript
const signupData = {
  email: userEmail,
  supalyticsVisitorId: window.supalytics?.visitorId || null,
};

await fetch('/api/signup', {
  method: 'POST',
  body: JSON.stringify(signupData),
});
```

### Server-side: Store with user record

```javascript
await db.user.create({
  data: {
    email: signupData.email,
    supalyticsVisitorId: signupData.supalyticsVisitorId,
  },
});
```

### At checkout: Use stored ID

```javascript
const user = await getUser(userId);

const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  subscription_data: {
    metadata: {
      supalytics_visitor_id: user.supalyticsVisitorId,
    },
  },
});
```

This works for all SaaS models:

* **Free plans**: Capture at free signup, use when they upgrade
* **Free trials**: Capture at trial start, use when they convert
* **Direct purchase**: See Cookie Mode below

***

## Cookie Mode: Capture at Checkout

In cookie mode, visitor IDs persist across sessions via cookies. A user can browse Monday and pay Friday—the ID stays the same. **You can capture at checkout.**

```javascript
// Client-side: capture current visitor ID
const visitorId = window.supalytics?.visitorId || null;

await fetch('/api/create-checkout', {
  method: 'POST',
  body: JSON.stringify({ visitorId }),
});
```

```javascript
// Server-side: pass directly to Stripe
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  subscription_data: {
    metadata: {
      supalytics_visitor_id: visitorId,
    },
  },
});
```

This also works for direct purchases without a signup step.

***

## Cookie Mode Without Consent Banner (Hybrid)

If you use cookie mode but don't want to show a consent banner:

* Non-EU visitors get persistent cookies automatically
* EU visitors fall back to privacy mode (daily-rotating IDs)

For EU visitors, you need to capture at signup (like privacy mode). You can detect EU visitors client-side:

```javascript
// At signup: capture visitor ID for EU users
const signupData = {
  email: userEmail,
  supalyticsVisitorId: window.supalytics?.visitorId || null,
  isEu: window.supalytics?.isEu || false,
};
```

```javascript
// At checkout: use stored ID for EU, fresh ID for non-EU
const user = await getUser(userId);

// For EU users, prefer the stored signup ID
// For non-EU users, the cookie ID works fine
const visitorId = user.isEu
  ? user.supalyticsVisitorId
  : (req.body.visitorId || user.supalyticsVisitorId);

const session = await stripe.checkout.sessions.create({
  subscription_data: {
    metadata: { supalytics_visitor_id: visitorId },
  },
});
```

Or simplify: just always capture at signup regardless of tracking mode—it works for both.

***

## Pass Visitor ID to Stripe

**For subscriptions** (most common):

```javascript
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  subscription_data: {
    metadata: {
      supalytics_visitor_id: visitorId,
    },
  },
});
```

**Important:** Use `subscription_data.metadata`, not top-level `metadata`. This ensures the visitor ID flows to all future charges—including trial conversions and renewals.

**For one-time payments:**

```javascript
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  payment_intent_data: {
    metadata: {
      supalytics_visitor_id: visitorId,
    },
  },
});
```

**For direct PaymentIntents** (without Checkout):

```javascript
const paymentIntent = await stripe.paymentIntents.create({
  amount: 1000,
  currency: 'usd',
  metadata: {
    supalytics_visitor_id: visitorId,
  },
});
```

### 3. Verify It's Working

After a payment:

1. Go to [Stripe Dashboard → Payments](https://dashboard.stripe.com/payments)
2. Click a recent transaction
3. Check the **Metadata** section
4. You should see `supalytics_visitor_id`

<img alt="Stripe metadata verification" src="/docs/stripe-metadata-verification.webp" width="2827" height="1635" />

### 4. View Revenue Data

Revenue syncs when you load your dashboard:

* Revenue breakdown by country, source, UTM parameters
* Revenue bars overlaid on visitor breakdown cards
* Hover tooltips showing visitors, revenue, and revenue per visitor

## Revenue Breakdown

The trends chart tooltip shows:

* **Revenue**: Total realized revenue
* **New**: First-time subscription + one-time payments
* **Renewal**: Recurring subscription payments
* **Trials**: Trial starts with potential revenue in parentheses
* **Refunds**: Money returned

### Potential Revenue

When a trial starts, Supalytics captures the plan price as "potential revenue." For example, 5 trials on a $49/month plan shows: `Trials: 5 ($245)`

## Privacy

**What we store:**

* Transaction amount and currency
* Encrypted charge ID (for deduplication)
* Anonymous visitor ID

**What we don't store:**

* Customer name, email, address
* Payment method details
* Any Stripe customer data

See [Tracking Modes](/docs/tracking-modes) for privacy details per mode.

## Unattributed Revenue

Revenue from customers who signed up before you added visitor ID tracking shows as "Unattributed." This decreases over time as new properly-tracked customers convert.

To fix existing subscriptions, see [Backfill Existing Subscriptions](/docs/backfill-existing-subscriptions).


---

## Other Documentation

- [Autocapture](https://www.supalytics.co/llms/docs/autocapture)
- [Backfill Existing Subscriptions](https://www.supalytics.co/llms/docs/backfill-existing-subscriptions)
- [Block Your Own Traffic](https://www.supalytics.co/llms/docs/block-your-traffic)
- [CLI](https://www.supalytics.co/llms/docs/cli)
- [Custom Events](https://www.supalytics.co/llms/docs/custom-events)
- [Features](https://www.supalytics.co/llms/docs/features)
- [Conversion Funnels](https://www.supalytics.co/llms/docs/funnels)
- [Introduction](https://www.supalytics.co/llms/docs)
- [Install Script](https://www.supalytics.co/llms/docs/install-script)
- [MRR Tracking](https://www.supalytics.co/llms/docs/mrr-tracking)
- [Agent Skills](https://www.supalytics.co/llms/docs/skills)
- [Tracking Modes](https://www.supalytics.co/llms/docs/tracking-modes)
- [Visitor Journey](https://www.supalytics.co/llms/docs/visitor-journey)
- [Annotations](https://www.supalytics.co/llms/docs/api/annotations)
- [Error Codes](https://www.supalytics.co/llms/docs/api/errors)
- [Events (Read)](https://www.supalytics.co/llms/docs/api/events)
- [API Reference](https://www.supalytics.co/llms/docs/api)
- [Journeys](https://www.supalytics.co/llms/docs/api/journeys)
- [Query API](https://www.supalytics.co/llms/docs/api/query)
- [Realtime API](https://www.supalytics.co/llms/docs/api/realtime)
- [Revenue Attribution API](https://www.supalytics.co/llms/docs/api/revenue-attribution)
- [Events (Write)](https://www.supalytics.co/llms/docs/api/server-side-events)