---
title: Backfill Existing Subscriptions
description: Add visitor IDs to existing Stripe subscriptions so future renewals are attributed
url: https://www.supalytics.co/docs/backfill-existing-subscriptions
---



If you already have users who signed up before implementing revenue attribution, you can backfill their visitor IDs so future renewals are attributed correctly.

## The Problem

Existing users already completed signup, so you missed capturing their original visitor ID. However, when they log in again, you can:

1. Capture their current visitor ID
2. Store it in your database (if they don't already have one)
3. Update their Stripe subscription metadata

This won't attribute their original signup to a traffic source, but it **will** attribute all future renewals to their next login source—which is better than having permanently unattributed revenue.

## What Gets Attributed

After implementing backfill, future renewals will be attributed to:

| Data Point     | Attributed |
| -------------- | ---------- |
| Country        | Yes        |
| City           | Yes        |
| Referrer       | Yes        |
| UTM parameters | Yes        |
| Device type    | Yes        |
| Browser        | Yes        |
| OS             | Yes        |

**Note:** The attributed data reflects when the visitor ID was captured—not their original signup. For example, if a user originally signed up from a Google ad on desktop but you capture their ID when they visit from mobile, renewals will show mobile attribution.

## Implementation

### Step 1: Add a Column to Store Visitor IDs

Add a column to your users/profiles table:

```sql
ALTER TABLE users
ADD COLUMN supalytics_visitor_id TEXT;
```

### Step 2: Capture Visitor ID on Login

When users log in, capture the visitor ID client-side and pass it to your server:

```javascript
// Client-side: capture on login
const handleLogin = async () => {
  const visitorId = window.supalytics?.visitorId || null;

  await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({
      email,
      password,
      supalyticsVisitorId: visitorId,
    }),
  });
};
```

### Step 3: Store Visitor ID (Preserve First-Touch)

On your server, only store the visitor ID if the user doesn't already have one. This preserves first-touch attribution:

```javascript
// Server-side: after successful authentication
const user = await getAuthenticatedUser();
const { supalyticsVisitorId } = requestBody;

if (supalyticsVisitorId) {
  // Check if user already has a visitor ID
  const existingUser = await db.user.findUnique({
    where: { id: user.id },
    select: { supalyticsVisitorId: true },
  });

  // Only update if no existing visitor ID (preserve first-touch)
  if (!existingUser.supalyticsVisitorId) {
    await db.user.update({
      where: { id: user.id },
      data: { supalyticsVisitorId },
    });
  }
}
```

### Step 4: Update Stripe Subscription Metadata

For existing users with active subscriptions, update their Stripe subscription metadata so renewals are attributed. If a user has multiple subscriptions, update all of them:

```javascript
// After storing the visitor ID, update Stripe subscriptions
if (supalyticsVisitorId && !existingUser.supalyticsVisitorId) {
  // Get user's active subscriptions from your database
  const subscriptions = await db.subscription.findMany({
    where: {
      userId: user.id,
      status: { in: ['active', 'trialing'] },
    },
  });

  for (const subscription of subscriptions) {
    await stripe.subscriptions.update(subscription.stripeSubscriptionId, {
      metadata: {
        supalytics_visitor_id: supalyticsVisitorId,
      },
    });
  }
}
```

**Tip:** Run this logic asynchronously so it doesn't block the login flow.

### OAuth Flows (Google, GitHub, etc.)

For OAuth-based authentication, you need to pass the visitor ID through the OAuth redirect flow. The challenge is that the user leaves your site to authenticate, then returns to a callback URL.

**Solution:** Pass the visitor ID as a URL parameter in the OAuth callback:

```javascript
// Client-side: before initiating OAuth
const handleOAuthLogin = async () => {
  const visitorId = window.supalytics?.visitorId || null;

  // Your server builds the OAuth URL with visitorId
  const response = await fetch('/api/auth/oauth-url', {
    method: 'POST',
    body: JSON.stringify({
      provider: 'google',
      supalyticsVisitorId: visitorId,
    }),
  });

  const { authUrl } = await response.json();
  window.location.href = authUrl;
};
```

```javascript
// Server-side: build OAuth URL with callback containing visitorId
const buildOAuthUrl = (provider, supalyticsVisitorId) => {
  const callbackUrl = new URL('/auth/callback', process.env.APP_URL);

  if (supalyticsVisitorId) {
    callbackUrl.searchParams.set('supalyticsVisitorId', supalyticsVisitorId);
  }

  // Use this callback URL when initiating OAuth
  return getOAuthUrl(provider, callbackUrl.toString());
};
```

```javascript
// Server-side: OAuth callback handler
const handleOAuthCallback = async (request) => {
  const url = new URL(request.url);
  const supalyticsVisitorId = url.searchParams.get('supalyticsVisitorId');

  // After authenticating user...
  const user = await authenticateOAuthUser(request);

  // Store visitor ID and update Stripe (same as Step 3 & 4)
  if (supalyticsVisitorId) {
    await storeVisitorIdAndUpdateStripe(user.id, supalyticsVisitorId);
  }

  return redirect('/dashboard');
};
```

## Attribution Summary

| Scenario                                  | Attribution                      |
| ----------------------------------------- | -------------------------------- |
| Existing user's past payments             | Unattributed (already processed) |
| Existing user logs in, renewal next month | Attributed to login source       |
| New user signs up with standard setup     | Attributed to signup source      |

## Other Approaches

The login-based approach above is one way to backfill visitor IDs. Depending on your app, you might prefer:

* **Capture on page load** — Run a background check when authenticated users load your dashboard. Catches users who stay logged in.
* **In-app prompt** — Show a modal or banner asking users to "refresh their session" which triggers the capture.

The core logic remains the same: capture `window.supalytics.visitorId`, store it if the user doesn't have one, and update their Stripe subscription metadata.


---

## Other Documentation

- [Autocapture](https://www.supalytics.co/llms/docs/autocapture)
- [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)
- [Revenue Attribution](https://www.supalytics.co/llms/docs/revenue-attribution)
- [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)