# How to track lead source in Stripe

Tie every Stripe charge, subscription, and renewal back to the marketing channel that drove it. Real revenue attribution, MRR by source, not just contact counts.

Source: https://sourceloop.ai/help/track-lead-source-in-stripe/
Updated: 2026-05-28

---

Stripe is brilliant at running checkout and recording who paid. What it can't tell you is **which marketing channel produced each dollar of MRR**, which is the question that actually matters when you're deciding where to spend next quarter's budget.

SourceLoop closes that loop. Every charge, every renewal, every refund mapped back to the visitor's original source, so "we made $X" becomes "we made $X, with most of it from organic SEO and the Reddit campaign breaking even".

Four steps, around ten minutes. Works on every Stripe plan, every checkout surface.

## Why this matters

Most attribution stops at the signup or the trial start. That works for one-off purchases. For SaaS and any subscription business, the real revenue lives downstream of the first event:

- A user signs up in June via a Google Ads campaign
- They start a free trial, then convert to a $99/mo plan in July
- They upgrade to a $199/mo plan in October
- They renew every month for two more years

Without revenue tracking, Google Ads gets credit for one "trial signup" and never sees the $5K+ of lifetime revenue that flowed from it. With Stripe wired into SourceLoop, every renewal, upgrade, and downgrade hangs off the same lead, and channel-level reporting reflects actual MRR, not just first-touch counts.

## What SourceLoop captures from Stripe

Each Stripe customer arrives in SourceLoop tagged with:

- **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct)
- **UTM parameters** from the landing URL of the converting session
- **Pages browsed** before the checkout was started
- **Time on site** and **return-visit count** ahead of the purchase
- **Email + name** from Stripe's customer record
- **First-touch landing page** plus the original referrer
- **Source of the converting session** (often distinct from first-touch)
- **Full revenue history**, one-off charges, subscription created / upgraded / downgraded / paused / canceled, recurring invoices (renewals), refunds and chargebacks
- **Device, country, browser**

## Before you start

You'll need:

- A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up))
- **Edit access** to the site that hosts your checkout button or Payment Link
- A **Stripe account** with a Checkout Session, Payment Link, or Payment Intent flow live
- **Developer or owner access** to your Stripe Dashboard, you'll need it to add a webhook endpoint

## Step 1: Install the SourceLoop tracking script

Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet.

![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png)

Paste it into the `<head>` of every page on your site, especially the pages that lead into Stripe checkout. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the payments Stripe will start sending.

## Step 2: Generate a webhook in your Stripe Dashboard

Stripe sends payment events as webhooks. SourceLoop gives you a dedicated webhook URL, you paste it into Stripe, and Stripe sends each event there alongside any other destinations you've already configured.

1. In SourceLoop, open **Setup -> Payment** in the sidebar. You'll see all supported payment providers (Stripe, Lemon Squeezy, Paddle, Polar, Dodo) listed as cards.

![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png)

2. Click the **Stripe** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you.

![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp)

3. Open the [Stripe Dashboard](https://dashboard.stripe.com/), navigate to **Developers -> Webhooks**, and click **Add endpoint**.
4. Paste SourceLoop's URL into the **Endpoint URL** field.
5. Under **Select events**, choose the events you want SourceLoop to receive. The recommended set: `charge.succeeded`, `charge.refunded`, `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.paid`.
6. Click **Add endpoint** to save.

> **Already have a Stripe webhook to your app?**
> You can add SourceLoop as an additional endpoint, no need to replace your existing one. Stripe delivers each event to every endpoint independently.

## Step 3: Paste the signing secret back into SourceLoop

Stripe verifies every webhook with a signing secret. You'll find it on the endpoint detail page you just created.

1. On the endpoint detail page in Stripe, find **Signing secret** and click **Reveal**.
2. Copy the `whsec_...` value.
3. Back in the same SourceLoop drawer, paste it into the **Webhook signing secret** field and click **Save**.

SourceLoop's connection status flips from **pending** to **active** the first time Stripe sends a verified event through.

## Step 4: Wire attribution into your checkout

The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, Stripe events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially for one-off purchases where the customer signs up at checkout.

Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor:

```js
window.sourceloop.checkoutMetadata()
// returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' }
```

Pass that object through to Stripe in whichever way matches your checkout surface. Use this guide to pick:

- **(1) Stripe Checkout** — The default for most businesses. If clicking **Buy**, **Subscribe**, **Upgrade**, **Checkout**, or **Pay** redirects the customer to a Stripe-hosted page on `checkout.stripe.com`, this is you. Covers SaaS subscriptions, one-off products, digital downloads, course sales, donations, anything that hands the customer off to Stripe's hosted checkout.
- **(2) Payment Intents and Subscriptions API** — When you render the card form yourself with Stripe Elements (no redirect, the form lives inside your app). Picked by custom checkouts, white-labeled flows, native mobile apps, and marketplaces that need full control over the checkout UX.
- **(3) Payment Links** — Static `buy.stripe.com/...` URLs you paste into tweets, marketing pages, email broadcasts, or affiliate links, no per-customer code creating the session. Lowest fidelity for attribution.

If you use more than one (e.g., Checkout for first-time signups and Payment Intents for in-app upgrades), wire each one.

### (1) Stripe Checkout (hosted checkout sessions)

The most common pattern. Capture the metadata on the client, send it to your backend with the price id, and set it on the Checkout Session when you create it. For subscription mode, also persist it onto `subscription_data.metadata` so every renewal event stays stitched.

Client:

```js
const meta = window.sourceloop.checkoutMetadata();

const res = await fetch('/api/create-checkout', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ priceId: 'price_xxx', meta }),
});
const { url } = await res.json();
window.location.href = url;
```

Server (Node.js):

```js
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: priceId, quantity: 1 }],
  success_url: 'https://your.site/thanks',
  cancel_url:  'https://your.site/pricing',

  // Attach to the Checkout Session (read by charge events)
  metadata: meta,

  // Persist onto the Subscription itself (read by every renewal)
  subscription_data: { metadata: meta },
});

return Response.json({ url: session.url });
```

### (2) Payment Intents and Subscriptions API

For custom Stripe Elements flows. Set `metadata` when you create the `PaymentIntent` or `Subscription` server-side. Subscription metadata is the strongest signal, it propagates to every future renewal event automatically.

One-shot PaymentIntent:

```js
const intent = await stripe.paymentIntents.create({
  amount: 9900,
  currency: 'usd',
  customer: customerId,
  metadata: meta,
});
```

Recurring Subscription:

```js
const sub = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: priceId }],
  metadata: meta,
});
```

### (3) Payment Links (buy.stripe.com)

Static "Buy" buttons from `buy.stripe.com`. Append `client_reference_id` to every Buy URL before the visitor clicks, Stripe stores it on the resulting Checkout Session, and SourceLoop reads it from the webhook event.

```html
[Subscribe](https://buy.stripe.com/your_link)

<script>
  document.querySelectorAll('a.stripe-buy').forEach(a => {
    const { sourceloop_anonymous_id } = window.sourceloop.checkoutMetadata();
    const u = new URL(a.href);
    u.searchParams.set('client_reference_id', sourceloop_anonymous_id);
    a.href = u.toString();
  });
</script>
```

> **Payment Links are the lowest-fidelity option**
> `client_reference_id` only carries the anonymous-id half of the stitching pair, not the full session id. If it's missing, SourceLoop falls back to matching by customer email. Use Checkout Sessions instead of Payment Links wherever you can.

> **Let your AI assistant do the wiring**
> If you'd rather not patch every Stripe integration point by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Stripe integration in this codebase. Show me the files you plan to touch before editing." It'll find every `stripe.checkout.sessions.create`, `stripe.paymentIntents.create`, and Buy URL on the site, and patch them in one pass.

## Step 5: Run a test charge to verify

Open the page with your Stripe checkout in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=stripe-check` to the URL, and complete a real card transaction. Stripe test mode is the safest path if you'd rather not move real money, just toggle your endpoint to **Listen to events on your test account** while testing.

Within seconds of Stripe confirming the charge, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact.

> **Not seeing the event?**
> Inside SourceLoop, open the Stripe connection card and check the **Last event** timestamp. If it's stale, the issue is on Stripe's webhook delivery side, check Stripe Dashboard -> Webhooks -> Logs for delivery errors. If the event is being received but the contact isn't linking, the most common cause is the customer's email not matching their pre-checkout session, which usually means the visitor never browsed your site before paying.

## Where to see Stripe revenue in SourceLoop

### Contacts Hub: revenue per contact

Every Stripe customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, original charge, every renewal, every upgrade, every refund, alongside the visitor's pre-purchase journey.

Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels are bringing in paying customers vs. just signups.

![SourceLoop Contacts Hub showing a Stripe customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp)

### Attribution dashboard: revenue by channel

[app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "Google CPC drove 240 sessions", you see "Google CPC drove 240 sessions and $8,400 in attributed Stripe revenue."

Switch attribution models from the dropdown at the top of the dashboard to see how the numbers shift:

- **Last Non-Direct** (the default, matches Google Analytics)
- **First Touch** / **First Non-Direct** for top-of-funnel weighting
- **Last Touch** for closing-channel weighting
- **Linear** to split credit evenly across every touchpoint
- **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle
- **Time Decay** to weight recent touches higher (7-day half-life)

Different models surface different stories. A campaign that looks weak under Last Touch may be your biggest top-of-funnel driver under First Touch, that's the value of being able to flip between them in one click.

![SourceLoop attribution dashboard with Stripe revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp)

Once Stripe revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward real paying customers, not trial signups or vanity clicks. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring.

## Frequently Asked Questions

### Does this work with Stripe Checkout, Payment Links, and Payment Intents?

Yes. All three checkout surfaces are covered. The webhook setup is the same for all of them, the only difference is in step 4, where you wire attribution metadata. The Attribution tab inside the Stripe drawer ships a snippet for each surface.

### What if I use more than one Stripe checkout surface (Checkout for sales, Payment Intents for in-app upgrades)?

Wire all of them. Each one gets its own attribution snippet. SourceLoop reads the same metadata keys regardless of which surface produced the event, so a customer who upgrades later via Payment Intents still ties back to their original Stripe Checkout signup.

### Are subscription renewals attributed back to the original source?

Yes. This is the whole point. Every renewal invoice gets stitched onto the same contact that started the subscription, so MRR and LTV stay tied to the channel that earned the original signup, not just the first charge.

### How do refunds and chargebacks show up?

Refunds and chargebacks flow into SourceLoop as negative revenue tied back to the original lead. The attribution dashboard nets them against the original source, so reported channel revenue stays accurate even months after the fact.

### I run a freemium product where signups happen first and charges happen later. Does that still tie back?

Yes, that's the most common SaaS pattern. The free signup creates the contact in SourceLoop with full attribution. When the user upgrades to paid weeks or months later, the Stripe event ties to the same email and attaches revenue to the original source.

### Will my existing Stripe webhooks to Zapier, HubSpot, or my data warehouse still fire?

Yes. SourceLoop receives its own copy of Stripe events via its own webhook endpoint. Every other destination you have configured continues to receive Stripe events independently.

### Is my Stripe webhook signing secret safe?

Yes. The secret is encrypted at rest the moment you save it, used only on SourceLoop's backend to verify webhook authenticity, and never exposed in the browser. You can rotate or revoke it in Stripe whenever you want.
