Back to Blog
February 18, 2026Tutorials

How to Pass UTM Parameters to Stripe (Track Revenue by Campaign)

Learn how to pass UTM parameters to Stripe so you can attribute revenue to specific campaigns. Code examples for Stripe Checkout metadata, server-side tracking, and automated solutions.

Written by Jamie Isabel
How to Pass UTM Parameters to Stripe

The Problem: UTMs Track Clicks, Not Revenue

You're running paid campaigns, UTM parameters are firing correctly, Google Analytics shows which campaigns drive traffic — but when someone actually pays, all that attribution data vanishes. Your Stripe dashboard has no idea which campaign generated the revenue.

This is the Stripe attribution gap, and it's costing you real insight into your marketing ROI. In this guide, I'll show you exactly how to pass UTM parameters to Stripe so every dollar of revenue ties back to the campaign that earned it.

Quick UTM Primer

If you're already familiar with UTM parameters, skip ahead. For everyone else — UTM parameters are tags you add to URLs to track where your traffic comes from. There are five standard ones:

  • utm_source — Where the traffic comes from (google, facebook, newsletter)
  • utm_medium — The marketing channel (cpc, email, paid_social)
  • utm_campaign — The specific campaign name
  • utm_term — The keyword (mainly for paid search)
  • utm_content — Differentiates ads or links within the same campaign

When someone clicks a link with UTMs, analytics tools pick them up and attribute the visit to the right campaign. The problem? That attribution stops at your analytics tool. It never reaches Stripe.

The Stripe Attribution Gap

Here's why UTMs don't flow into Stripe automatically:

  • Stripe is a payment processor, not an analytics tool. It processes transactions — it doesn't know or care how a customer found your website.
  • The checkout redirect breaks the chain. When you redirect to Stripe Checkout, the UTM parameters from the original landing page don't carry over.
  • No built-in UTM support. Stripe has no native field for UTM data. You have to explicitly pass it.

The result? You can see that 100 people came from your Google Ads campaign, and that you made $5,000 in Stripe this month. But you can't connect the two.

Method 1: Client-Side UTM Capture

The first step in any approach is capturing UTM parameters when a visitor lands on your site. Here's a JavaScript function that grabs UTMs from the URL and stores them in cookies:

function captureUTMs() {
  const params = new URLSearchParams(window.location.search);
  const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
  const utms = {};

  utmKeys.forEach(key => {
    const value = params.get(key);
    if (value) {
      utms[key] = value;
      document.cookie = `${key}=${encodeURIComponent(value)}; path=/; max-age=${60 * 60 * 24 * 30}`;
    }
  });

  return utms;
}

// Run on page load
captureUTMs();

This stores UTMs in cookies that last 30 days. Even if the visitor browses 10 pages before checking out, you still have the original UTM data. You can also use localStorage instead — it doesn't expire automatically but isn't sent with server requests.

Method 2: Pass UTMs as Stripe Checkout Metadata

This is the core technique. Stripe Checkout Sessions accept a metadata field — a key-value store you can attach to any session.

Frontend: Read stored UTMs and send to your server

async function createCheckout() {
  const getCookie = (name) => {
    const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
    return match ? decodeURIComponent(match[2]) : '';
  };

  const response = await fetch('/api/create-checkout-session', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      priceId: 'price_xxx',
      utm_source: getCookie('utm_source'),
      utm_medium: getCookie('utm_medium'),
      utm_campaign: getCookie('utm_campaign'),
      utm_term: getCookie('utm_term'),
      utm_content: getCookie('utm_content'),
    }),
  });

  const { url } = await response.json();
  window.location = url;
}

Backend: Create Stripe Checkout Session with UTM metadata

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/api/create-checkout-session', async (req, res) => {
  const { priceId, utm_source, utm_medium, utm_campaign, utm_term, utm_content } = req.body;

  const session = await stripe.checkout.sessions.create({
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'subscription',
    success_url: 'https://yoursite.com/success',
    cancel_url: 'https://yoursite.com/pricing',
    metadata: {
      utm_source: utm_source || '',
      utm_medium: utm_medium || '',
      utm_campaign: utm_campaign || '',
      utm_term: utm_term || '',
      utm_content: utm_content || '',
    },
  });

  res.json({ url: session.url });
});

Now every Stripe Checkout Session carries the UTM data that brought the customer to your site. You can see it in the Stripe Dashboard under the payment's metadata section.

Method 3: Server-Side Session Tracking

For more robust tracking — especially for SaaS products where there's a gap between signup and payment — store UTMs in your database tied to the user's account:

// When user signs up, store their UTMs
await db.users.update({
  where: { id: userId },
  data: {
    utm_source: req.cookies.utm_source || null,
    utm_medium: req.cookies.utm_medium || null,
    utm_campaign: req.cookies.utm_campaign || null,
    utm_term: req.cookies.utm_term || null,
    utm_content: req.cookies.utm_content || null,
  },
});

// Later, when creating a Stripe customer
const user = await db.users.findUnique({ where: { id: userId } });

const customer = await stripe.customers.create({
  email: user.email,
  metadata: {
    utm_source: user.utm_source || '',
    utm_medium: user.utm_medium || '',
    utm_campaign: user.utm_campaign || '',
  },
});

The UTMs are stored in your database and attached to the Stripe customer whenever they convert — even if it's days or weeks later.

Method 4: The Easy Way — UTM Helper

All of the methods above work, but they require custom code, maintenance, and careful handling of edge cases — cookie expiration, cross-domain tracking, ad blockers, and more.

UTM Helper automates this entire pipeline:

  • Captures UTMs automatically on every landing page
  • Persists them across pages — no UTM data lost when visitors navigate
  • Passes UTMs to forms — hidden fields auto-populated for CRM attribution
  • Integrates with Stripe — UTM data flows into your payment metadata without custom code
<script defer data-domain="yourdomain.com" src="https://utmhelper.com/your-script.js"></script>

One script tag. No cookie management code. No metadata plumbing. No maintenance.

Building a Revenue Attribution Dashboard

Once UTMs are in Stripe, you can build a revenue attribution dashboard. Use the Stripe API to pull payments with their metadata:

const payments = await stripe.charges.list({ limit: 100 });

const revenueBySource = {};

payments.data.forEach(charge => {
  const source = charge.metadata.utm_source || 'direct';
  if (!revenueBySource[source]) {
    revenueBySource[source] = { revenue: 0, count: 0 };
  }
  revenueBySource[source].revenue += charge.amount / 100;
  revenueBySource[source].count += 1;
});

console.log(revenueBySource);
// { google: { revenue: 3200, count: 18 }, facebook: { revenue: 1800, count: 12 }, ... }

You can also use Stripe's webhook events (checkout.session.completed) to push attribution data into your analytics warehouse in real-time.

Common Mistakes and Gotchas

1. Metadata value limits

Stripe metadata values max out at 500 characters. UTM values are typically short, but watch out for overly long campaign names.

2. Not storing UTMs on first touch

If you only capture UTMs at checkout time, you miss the original source. Capture on landing, store immediately.

3. Cookie expiration too short

A 30-day cookie window is standard, but for B2B with long sales cycles, consider 90 days.

4. Forgetting to propagate to subscriptions

If you set metadata on the Checkout Session but not the Customer or Subscription, you'll lose it when querying later. Set it on all three.

5. Mixed case UTM values

Google and google are different in Stripe metadata. Normalize to lowercase when capturing.

6. Not handling direct traffic

If someone visits without UTMs, store direct or leave empty. Don't let it silently break your attribution logic.

Wrapping Up

Passing UTM parameters to Stripe isn't hard — it just requires connecting a few pieces that don't talk to each other by default. Capture UTMs on landing, store them, pass them as Stripe metadata, and suddenly you can answer the most important marketing question: which campaigns actually generate revenue?

If you want to skip the custom code, UTM Helper handles the entire flow from ad click to Stripe metadata automatically.

Stop losing UTM data

UTM Helper persists campaign parameters across pages and auto-fills them into your forms. One script tag, full-funnel attribution.

Get Started Free →