Launching Our Partner Program: Building a Growth Engine With Attribution
Why We Built Our Own Attribution System
Partnerships can be a massive growth lever for SaaS. The math is simple: if affiliates bring customers at half the cost of paid ads, and those customers have the same LTV, you’ve doubled your marketing efficiency.
But partnerships only work if tracking works. Affiliates need to trust they’re getting credited. You need to know which partnerships are actually driving value.
We could have used an off-the-shelf solution like PartnerStack or Tapfiliate. But we wanted:
- Full control over the attribution logic
- No per-transaction fees eating into our margins
- Flexibility to support multiple affiliate platforms
- Deep integration with our checkout flow
So we built it ourselves.
The Tracking Architecture
Our affiliate system has three components:
1. URL Parameter Detection
Affiliates share links with tracking parameters. We support the most common patterns:
const AFFILIATE_PARAMS = ['ref', 'via', 'affiliate', 'partner', 'tap_a'] as const;
export function parseAffiliateCode(url: string): string | null {
const urlObj = new URL(url, 'https://webzum.com');
const params = urlObj.searchParams;
// Check each parameter name in priority order
for (const paramName of AFFILIATE_PARAMS) {
const value = params.get(paramName);
if (value?.trim()) {
return value.trim();
}
}
return null;
}
Why multiple parameters? Different affiliate platforms use different conventions:
ref- Generic, widely usedvia- PartnerStack’s conventionaffiliate- Explicit, self-documentingpartner- For strategic partners vs. affiliatestap_a- Tapfiliate’s parameter
By supporting all of them, partners can use their preferred platform’s links without any custom configuration.
2. Cookie Persistence
Users rarely buy on the first visit. We use cookies to remember attribution across sessions:
export const AFFILIATE_COOKIE_NAME = 'webzum_affiliate';
export const AFFILIATE_COOKIE_DURATION_DAYS = 90;
export function setAffiliateCookie(affiliateCode: string): void {
const expires = new Date();
expires.setDate(expires.getDate() + AFFILIATE_COOKIE_DURATION_DAYS);
document.cookie = `${AFFILIATE_COOKIE_NAME}=${affiliateCode}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
}
90 days is the industry standard for B2B SaaS. It balances fair attribution (the affiliate introduced the customer) with recency (the intro actually mattered).
We use SameSite=Lax for security while still allowing cross-site navigation. Strict would break affiliate links from email campaigns.
3. Checkout Integration
The critical piece: passing affiliate codes to Stripe at checkout time.
// In create-checkout route
const affiliateCode = getAffiliateCodeFromCookie(req.headers.get('cookie'));
const session = await stripe.checkout.sessions.create({
// ... standard checkout config
metadata: {
userId,
businessId,
priceId,
...(affiliateCode && { affiliate_code: affiliateCode })
}
});
When a payment succeeds, the webhook receives this metadata:
// In webhook handler
async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
const affiliateCode = session.metadata?.affiliate_code;
if (affiliateCode) {
console.log(`💰 [AFFILIATE] Conversion attributed to: ${affiliateCode}`);
// TODO: Fire postback to affiliate platform
// TODO: Record in analytics
}
}
The Client Component
Tracking needs to initialize on page load. We built a React component that handles this:
'use client';
import { useEffect } from 'react';
import { initAffiliateTracking } from '@/lib/affiliate';
export default function AffiliateTracker() {
useEffect(() => {
const code = initAffiliateTracking();
if (code) {
console.log('[Affiliate] Tracking initialized:', code);
}
}, []);
return null; // Invisible component
}
This component lives in our root layout. Every page load checks for affiliate parameters and persists them.
The /partners Landing Page
We built a dedicated page for potential partners:
- Value proposition: “Earn 30% recurring commission”
- How it works: Simple 3-step explanation
- Partner tiers: Affiliate, Referral Partner, Strategic Partner
- Application form: Captures partner info for approval
The page uses the same dark theme as our tools pages to signal “this is for serious business partners.”
First-Touch vs. Last-Touch Attribution
We chose first-touch attribution: the first affiliate who introduces a customer gets credit, even if another affiliate’s link is clicked later.
Why? It’s fairer. The hardest part of marketing is initial awareness. If Partner A introduces a lead and Partner B retargets them a month later, Partner A did the real work.
export function initAffiliateTracking(): string | null {
const urlCode = parseAffiliateCode(window.location.href);
if (urlCode) {
// Only set if no existing cookie (first-touch)
const existingCode = getAffiliateCodeFromCookie(document.cookie);
if (!existingCode) {
setAffiliateCookie(urlCode);
return urlCode;
}
return existingCode; // Keep existing attribution
}
return getAffiliateCodeFromCookie(document.cookie);
}
Actually, looking at our code, we overwrite on new URL codes. That’s last-touch. Something to reconsider. First-touch builds better partner relationships.
What We’re Still Building
Postback Integration
When a conversion happens, we need to notify the affiliate platform. Different platforms have different postback formats:
// Coming soon
async function fireAffiliatePostback(affiliateCode: string, conversionValue: number) {
// Determine which platform based on code format
if (affiliateCode.startsWith('ps_')) {
await partnerStackPostback(affiliateCode, conversionValue);
} else if (affiliateCode.startsWith('tap_')) {
await tapfiliatePostback(affiliateCode, conversionValue);
} else {
// Generic webhook
await genericPostback(affiliateCode, conversionValue);
}
}
Partner Dashboard
Partners need to see their performance:
- Clicks
- Conversions
- Commission earned
- Payout status
We’re building this as a simple dashboard that partners can access with their affiliate code.
Automated Payouts
Currently manual. We’ll integrate with PayPal Mass Pay or Wise for automated monthly payouts once volume justifies it.
The Growth Thesis
Here’s why we’re investing in partnerships early:
- Trust transfer: When a trusted advisor recommends WebZum, their credibility transfers to us
- Qualified leads: Partners who understand their audience send us better-fit customers
- Compounding: Good partners promote consistently, creating predictable growth
- Lower CAC: 30% commission is cheaper than most paid acquisition channels
The hard part is finding good partners. The tracking system is just infrastructure. The real work is partner recruitment and relationship building.
What We Learned
Building affiliate tracking taught us a few things:
-
Cookie consent matters: We need to handle GDPR properly. Affiliate cookies are functional, not marketing, but the line is blurry.
-
Attribution is political: Every affiliate thinks they deserve credit. Clear, transparent rules (published in our partner agreement) prevent disputes.
-
Integration testing is hard: Simulating the full flow (click → cookie → website build → checkout → payment → webhook) requires careful test setup.
-
Start simple: We launched with basic tracking and will iterate. Perfect is the enemy of shipped.
The Numbers So Far
One week in:
- Partners signed up: 12
- Tracked clicks: 340+
- Conversions: 3
- Commission owed: ~$60
Small numbers, but it’s early. The system works. Now we scale.
Shipped December 12, 2025. DM us if you want to be a WebZum partner.