What Is Referral Tracking?
Referral tracking is the process of identifying which customers were referred by an affiliate or partner and attributing payments to that referral source. When someone clicks an affiliate link, visits your site, and eventually subscribes through Stripe, referral tracking connects those dots so you can calculate and pay the correct commission.
Stripe does not include a built-in affiliate or referral tracking system. It handles payments, subscriptions, and billing, but the attribution layer (who referred this customer?) must be built separately. This is where most SaaS companies either build custom solutions or use dedicated affiliate tracking software.
In this guide, we cover both approaches: the manual, build-it-yourself method for teams that want full control, and the turnkey approach using purpose-built tools. By the end, you will understand exactly how referral attribution works at the Stripe webhook level and be able to choose the best approach for your situation.
Why server-side tracking matters
Client-side JavaScript tracking (like Google Analytics or simple UTM parameters) is unreliable for commission calculations. Ad blockers strip tracking parameters, users switch devices between clicking a link and purchasing, and browser cookie policies have become increasingly restrictive. For financial operations like affiliate commissions, you need server-side verification. Stripe webhooks provide this: when a payment succeeds, Stripe sends your server a signed event with full payment details. Your server then matches that payment to a referral source.
The Three Tracking Methods
There are three primary methods for attributing a Stripe payment to a referral source. Most production systems combine all three in a priority chain, using whichever method is available for a given transaction.
Method 1: Cookie-based tracking
When a visitor clicks an affiliate link, you store the affiliate identifier in a first-party cookie. When that visitor later signs up, your server reads the cookie and records the attribution in your database. When a Stripe payment arrives via webhook, you look up the customer's email to find their attribution record.
Pros: Works for any customer journey, no changes needed to checkout flow. Cons: Cookies can be blocked or expire. Cross-device journeys break attribution.
Method 2: Coupon/promotion code tracking
Affiliates share unique coupon or promotion codes. When a customer applies the code at checkout, Stripe records which promotion code was used. Your webhook handler looks up which affiliate owns that code and attributes the payment accordingly.
Pros: Works across devices and browsers. No cookies needed. Great for influencer-style promotion. Cons: Customer must remember to enter the code. Some customers forget or find codes elsewhere.
Method 3: Metadata/client_reference_id tracking
When creating a Stripe Checkout session, you pass the affiliate identifier in the client_reference_id field or in the session/subscription metadata. This embeds the referral source directly in the Stripe data, making attribution deterministic.
Pros: Most reliable method. Data lives in Stripe itself. Cons: Requires modifying your checkout flow to pass the referral code.
Setting Up Stripe Webhooks
Stripe webhooks are HTTP POST requests that Stripe sends to your server when events occur in your account: successful payments, subscription changes, refunds, and more. For referral tracking, you primarily need to listen to three events:
checkout.session.completed- When a customer completes a checkoutinvoice.paid- When a subscription invoice is paid (recurring payments)charge.refunded- When a payment is refunded (to reverse commissions)
Step 1: Create a webhook endpoint
First, create an endpoint on your server to receive Stripe events. This endpoint must accept POST requests with a raw body (not parsed as JSON) because you need the raw body for signature verification.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// IMPORTANT: Webhook route must use raw body parser
// Register BEFORE express.json() middleware
app.post('/webhook/stripe',
express.raw({ type: 'application/json' }),
async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(event.data.object);
break;
case 'invoice.paid':
await handleInvoicePaid(event.data.object);
break;
case 'charge.refunded':
await handleRefund(event.data.object);
break;
}
res.json({ received: true });
}
);express.json() globally, it will break signature verification. Register the webhook route before your JSON body parser, or exclude /webhook paths from JSON parsing.Step 2: Register the webhook in Stripe Dashboard
Go to Stripe Dashboard → Developers → Webhooks and add your endpoint URL. Select the events you need: checkout.session.completed, invoice.paid, and charge.refunded. Stripe will provide a webhook signing secret (starts with whsec_). Store this in your environment variables as STRIPE_WEBHOOK_SECRET.
Step 3: Verify webhook signatures
Always verify the webhook signature using the signing secret. This prevents attackers from sending fake events to your endpoint. The stripe.webhooks.constructEvent() method handles this automatically. Never skip this verification in production.
Cookie-Based Tracking Implementation
Cookie-based tracking requires three components: a landing page script that captures the referral source, a server-side handler that records the attribution at signup, and webhook logic that resolves the affiliate from the customer email.
Capture the referral source
When a visitor arrives via an affiliate link like yourapp.com/?ref=AFFILIATE_CODE, your landing page script reads the ref parameter and stores it in a first-party cookie.
// tracking.js - Add to your marketing site
(function() {
const params = new URLSearchParams(window.location.search);
const ref = params.get('ref');
if (ref) {
// Store as first-party cookie, 90-day expiry
const expires = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000);
document.cookie = `ref=${ref}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
}
})();Record attribution at signup
When the visitor creates an account, your server reads the referral cookie and stores the association in your database.
// During user registration
app.post('/register', async (req, res) => {
const { email, password } = req.body;
const refCode = req.cookies.ref; // Read referral cookie
// Create user account
const user = await createUser(email, password);
// If referred, store the attribution
if (refCode) {
await db.query(
'INSERT INTO referral_attributions (user_email, referral_code, created_at) VALUES ($1, $2, NOW())',
[email, refCode]
);
}
res.redirect('/dashboard');
});Resolve affiliate in webhook handler
When a Stripe payment webhook arrives, look up the customer email in your attribution table to find the referring affiliate.
async function handleCheckoutCompleted(session) {
const customerEmail = session.customer_details?.email;
if (!customerEmail) return;
// Look up referral attribution
const { rows } = await db.query(
'SELECT referral_code FROM referral_attributions WHERE user_email = $1',
[customerEmail]
);
if (rows.length === 0) return; // Not a referred customer
const affiliateCode = rows[0].referral_code;
const amount = session.amount_total / 100; // Convert from cents
// Record conversion and calculate commission
await recordConversion({
affiliateCode,
customerEmail,
amount,
stripeSessionId: session.id,
});
}Coupon Code Attribution
Coupon-based tracking is particularly effective for influencer partnerships and content creators who share discount codes with their audience. The flow works differently from cookie tracking: the attribution signal comes from Stripe itself, not from your database.
Create promotion codes in Stripe
For each affiliate, create a Stripe promotion code linked to a coupon. The promotion code is what the customer types at checkout (e.g., "PARTNER20"), while the coupon defines the discount.
// Create a coupon (defines the discount)
const coupon = await stripe.coupons.create({
percent_off: 20,
duration: 'once',
name: 'Partner Discount',
});
// Create a promotion code for each affiliate
const promoCode = await stripe.promotionCodes.create({
coupon: coupon.id,
code: 'PARTNER20', // What the customer enters
metadata: {
affiliate_id: '42', // Link to your affiliate record
affiliate_code: 'john_smith',
},
});Handle promotion code in webhooks
When a checkout session completes with a promotion code, Stripe includes the discount information in the event. Your webhook handler extracts the promotion code and looks up the associated affiliate.
async function handleCheckoutCompleted(session) {
// Check for promotion code usage
if (session.discount?.promotion_code) {
const promoCode = await stripe.promotionCodes.retrieve(
session.discount.promotion_code
);
const affiliateId = promoCode.metadata?.affiliate_id;
if (affiliateId) {
await recordConversion({
affiliateId,
customerEmail: session.customer_details.email,
amount: session.amount_total / 100,
source: 'coupon',
});
return; // Attribution found via coupon
}
}
// Fall back to cookie-based attribution
await handleCookieAttribution(session);
}promotion_code field, not the coupon field. Multiple promotion codes can share the same coupon. The promotion code contains the affiliate metadata, the coupon does not.Metadata-Based Tracking
The most reliable tracking method passes the affiliate identifier directly into the Stripe Checkout session. This eliminates dependency on cookies and works even without coupon codes.
Pass referral data in checkout session
When creating a Stripe Checkout session, include the referral code in the client_reference_id field or in the session metadata.
// When creating a checkout session
app.post('/create-checkout', async (req, res) => {
const refCode = req.cookies.ref || req.body.ref;
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{
price: 'price_XXXXX',
quantity: 1,
}],
// Pass referral code directly to Stripe
client_reference_id: refCode || undefined,
metadata: {
referral_code: refCode || '',
},
// Also store in subscription metadata for recurring payments
subscription_data: {
metadata: {
referral_code: refCode || '',
},
},
success_url: `${process.env.APP_URL}/success`,
cancel_url: `${process.env.APP_URL}/pricing`,
});
res.json({ url: session.url });
});Read metadata in webhook handler
In your webhook handler, check the client_reference_id first, then fall back to metadata, then to cookie-based attribution.
async function resolveAffiliate(session) {
// Priority 1: client_reference_id
if (session.client_reference_id) {
const affiliate = await findAffiliateByCode(session.client_reference_id);
if (affiliate) return { affiliate, source: 'client_reference_id' };
}
// Priority 2: Promotion code
if (session.discount?.promotion_code) {
const promo = await stripe.promotionCodes.retrieve(
session.discount.promotion_code
);
if (promo.metadata?.affiliate_id) {
const affiliate = await findAffiliateById(promo.metadata.affiliate_id);
if (affiliate) return { affiliate, source: 'coupon' };
}
}
// Priority 3: Session metadata
if (session.metadata?.referral_code) {
const affiliate = await findAffiliateByCode(session.metadata.referral_code);
if (affiliate) return { affiliate, source: 'metadata' };
}
// Priority 4: Email fallback (cookie-based attribution from signup)
const email = session.customer_details?.email;
if (email) {
const attribution = await findAttributionByEmail(email);
if (attribution) return { affiliate: attribution, source: 'email_fallback' };
}
return null; // No affiliate found
}Testing Your Setup
Testing affiliate tracking before going live is non-negotiable. Broken tracking means wrong commissions, which means broken trust with your affiliates. Here is a systematic testing checklist.
Use Stripe test mode
Stripe's test mode lets you simulate the entire payment flow without real money. Use test card numbers (like 4242 4242 4242 4242) to create test subscriptions that trigger real webhooks to your endpoint.
Test each tracking method independently
- Cookie tracking: Visit your site with a
?ref=TEST_CODEparameter. Verify the cookie is set. Complete a test purchase. Check that the conversion is attributed to the correct affiliate. - Coupon tracking: Create a test promotion code with affiliate metadata. Complete a purchase using that code. Verify the webhook handler resolves the affiliate correctly.
- Metadata tracking: Create a checkout session with a
client_reference_id. Complete the purchase. Verify the attribution in your database.
Test edge cases
- What happens when a customer is referred by cookie but also uses a coupon from a different affiliate? Your priority chain should handle this consistently.
- What happens when a subscription renews? The
invoice.paidevent should attribute recurring commissions to the original referrer. - What happens on refund? The
charge.refundedevent should reverse or flag the commission. - What happens when a customer upgrades or downgrades? Commission recalculation should reflect the new amount.
Monitor with Stripe CLI
The Stripe CLI lets you forward webhooks to your local development server for real-time testing.
# Install Stripe CLI and login
stripe login
# Forward webhooks to your local server
stripe listen --forward-to localhost:3000/webhook/stripe
# In another terminal, trigger test events
stripe trigger checkout.session.completedGoing Live Checklist
Before switching from test mode to production, verify every item on this checklist.
Pre-launch checklist
- Webhook endpoint uses HTTPS (Stripe requires it for production)
- Webhook signature verification is enabled and tested
- All three tracking methods (cookie, coupon, metadata) are implemented and tested
- Attribution priority chain handles conflicts deterministically
- Recurring payment handling (invoice.paid) attributes to original referrer
- Refund handling (charge.refunded) reverses or flags commissions
- Commission calculation is correct for all plan tiers
- Webhook endpoint returns 200 quickly (long processing runs async)
- Webhook retry handling prevents duplicate commission recording
- Database has unique constraints to prevent duplicate conversions
- Monitoring/alerting is set up for webhook failures
- Cookie domain and SameSite settings are correct for production
Or Skip the DIY Approach
Building Stripe referral tracking from scratch is a substantial engineering investment. The basic implementation shown in this guide covers the core flow, but production systems need to handle dozens of additional edge cases: multi-currency conversions, trial periods, subscription pauses, plan migrations, coupon stacking, cross-device attribution, fraud detection, payout calculations, tax reporting, and ongoing maintenance as Stripe updates their API.
Most SaaS companies find that the engineering time spent building and maintaining custom tracking would be better invested in their core product. A purpose-built affiliate tracking platform handles all of these complexities out of the box, typically for less than the cost of a few hours of developer time per month.
Refgrow integrates with Stripe at the webhook level, implementing all three tracking methods described in this guide (cookies, coupon codes, and metadata) plus additional attribution layers. It handles recurring commissions, refunds, subscription changes, fraud detection, and automated payouts. Setup takes under 10 minutes: connect your Stripe account, paste the tracking script, and your affiliate program is live.
Skip months of engineering work
Refgrow handles Stripe referral tracking, commission calculations, fraud detection, and affiliate payouts. Connect your Stripe account and launch in under 10 minutes.
Related Tools and Resources
Stripe Affiliate Software
Complete Stripe affiliate integration, no code required.
Stripe Fee Calculator
Calculate Stripe processing fees for any transaction.
Affiliate Commission Calculator
Find the optimal commission rate for your SaaS.
Referral Link Generator
Generate tracking-ready referral links instantly.