惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
V
Visual Studio Blog
The GitHub Blog
The GitHub Blog
Apple Machine Learning Research
Apple Machine Learning Research
J
Java Code Geeks
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
人人都是产品经理
人人都是产品经理
H
Heimdal Security Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Cyberwarzone
Cyberwarzone
Google DeepMind News
Google DeepMind News
雷峰网
雷峰网
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
A
About on SuperTechFans
V2EX - 技术
V2EX - 技术
小众软件
小众软件
博客园 - Franky
博客园 - 司徒正美
P
Privacy International News Feed
爱范儿
爱范儿
U
Unit 42
博客园 - 叶小钗
The Hacker News
The Hacker News
C
Check Point Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
D
Docker
T
Threatpost
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Help Net Security
L
LINUX DO - 最新话题
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
A
Arctic Wolf
Spread Privacy
Spread Privacy

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Self-Hosted Affiliate Tracking Software for SaaS: Own Your Data, Own Your Growth
Zekariyas Berihun · 2026-05-30 · via DEV Community

You're building a SaaS. You want affiliates. You sign up for a third-party platform, paste in an embed snippet, and your program is live in 20 minutes.

Six months later you decide to switch tools — better pricing, a feature you need, or the platform gets acquired and the product direction changes. You export your CSV. And then you realize what you've actually lost.

The CSV has affiliate names, emails, and a payout history. What it doesn't have: the actual tracking infrastructure. The referral links are dead. The cookie logic lived on their servers. The conversion attribution pipeline is gone. You don't just migrate data — you rebuild from scratch. Every affiliate needs a new link. Every integration needs to be rewired. The "switch" that should take a weekend takes a month.

This is the core problem with third-party affiliate platforms that nobody writes about. It's not just the monthly fee. It's that switching costs compound invisibly until the day you try to leave.


The Real Cost of Third-Party Affiliate Platforms

Your Data Is Theirs Until You Leave

When you run your affiliate program on a closed-source platform, your conversion events, click logs, affiliate relationships, and commission history all live in their database schema. You get access through their UI and, if you're lucky, a reasonably complete API.

The moment you stop paying, access is revoked. The CSV they give you is a snapshot — flat, denormalized, stripped of relational context. You lose:

  • Click-level attribution data — which specific link, which campaign, which landing page variant drove each conversion
  • Time-series granularity — daily click and conversion curves that let you spot seasonal patterns or campaign spikes
  • Custom metadata — any extra fields you attached to affiliate signups or conversion events
  • The tracking layer itself — the referral link format, the cookie domain, the JavaScript snippet. All of it belongs to them. You can export the ledger. You cannot export the engine.

Switching Means Starting Over

Here's what a migration from a third-party affiliate platform actually looks like in practice:

  1. Referral links break immediately. Every affiliate has been sharing yourplatform.com/ref/affiliate-slug style links that route through the platform's redirect infrastructure. Those links die the moment you cancel. Your affiliates are now sending traffic to dead URLs.
  2. Attribution history is orphaned. Even if you have the CSV, your new system has no way to resolve historical conversions back to the correct affiliate unless you manually re-import and remap every record. Most platforms export commission totals, not the raw conversion events that produced them.
  3. Affiliate trust takes a hit. You have to email every active affiliate, explain the migration, give them new links, and ask them to update everything they've published — blog posts, YouTube descriptions, newsletters. Some won't. Those referrals are permanently lost.
  4. You rebuild the integration anyway. The Stripe webhook handler, the checkout metadata, the cookie capture logic — you write all of it fresh regardless of which new platform you move to. The only question is whether you're writing it to feed another third-party system or your own.

The Lock-In Is Structural, Not Accidental

This isn't a bug in how these platforms work. It's the business model. The harder you are to leave, the more pricing power they have over you. A platform that makes migration easy is a platform that competes on merit every renewal cycle. Most don't want that.

Self-hosted affiliate tracking software eliminates this dynamic entirely. Your data is in your database, in your schema, accessible to your queries. Your tracking logic is in your codebase. Switching tools means updating a config, not rebuilding an affiliate program.


How Affiliate Tracking Actually Works Under the Hood

Before you self-host anything, you need a clear mental model of what "affiliate tracking" actually consists of. It's three distinct components:

1. Attribution — Capturing the Referral

When a visitor arrives via an affiliate link (yourapp.com?ref=affiliate-slug), something needs to capture that ref parameter and persist it until conversion. The standard approach: read the query param, write it to a cookie with a 30–90 day expiration.

// Simplified attribution logic — runs when a visitor lands
function captureAffiliateRef(searchParams: URLSearchParams): void {
  const ref = searchParams.get('ref');
  if (!ref || !isValidSlug(ref)) return;

  // Only set on first touch — first-click attribution
  if (document.cookie.includes('affiliate_ref=')) return;

  const maxAge = 60 * 60 * 24 * 30; // 30 days
  document.cookie = `affiliate_ref=${encodeURIComponent(ref)}; max-age=${maxAge}; path=/; SameSite=Lax`;
}

function isValidSlug(slug: string): boolean {
  return /^[a-zA-Z0-9][a-zA-Z0-9-]{1,39}$/.test(slug);
}

Enter fullscreen mode Exit fullscreen mode

First-click attribution (shown above) gives credit to the first affiliate who drove the visit. The cookie isn't overwritten if it already exists. This is the most common and fair model for SaaS affiliate programs.

2. Conversion — Linking the Payment to the Referral

When the user completes a paid action, you read the cookie and attach the affiliate reference to the payment event. With Stripe, this means passing it as session metadata at checkout creation, then reading it back in the webhook.

// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20',
});

export async function POST(request: NextRequest): Promise<NextResponse> {
  // Read the affiliate cookie from the incoming request
  const affiliateRef = request.cookies.get('affiliate_ref')?.value ?? null;

  const sessionParams: Stripe.Checkout.SessionCreateParams = {
    mode: 'subscription',
    line_items: [{ price: process.env.STRIPE_PRICE_ID!, quantity: 1 }],
    success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
  };

  if (affiliateRef) {
    // Attach to session — readable in checkout.session.completed webhook
    sessionParams.metadata = { affiliate_ref: affiliateRef };

    // Attach to subscription — readable on every invoice for recurring commissions
    sessionParams.subscription_data = {
      metadata: { affiliate_ref: affiliateRef },
    };
  }

  const session = await stripe.checkout.sessions.create(sessionParams);
  return NextResponse.json({ url: session.url });
}

Enter fullscreen mode Exit fullscreen mode

Why attach to both session and subscription?
The checkout.session.completed event fires once. But if you're paying recurring commissions (e.g., 20% of every renewal), you need the ref on the subscription object so it's available on every invoice.payment_succeeded event going forward.

3. Confirmation — Webhook-Driven Commission Recording

Never trust the client-side success page for conversion confirmation. Users close tabs, skip redirects, or hit back. The Stripe webhook is the authoritative signal.

// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20',
});

export async function POST(request: NextRequest): Promise<NextResponse> {
  const body = await request.text();
  const sig = request.headers.get('stripe-signature');

  if (!sig) return NextResponse.json({ error: 'No signature' }, { status: 400 });

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // Initial conversion
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session;
    const ref = session.metadata?.affiliate_ref;

    if (ref && session.amount_total) {
      await db.affiliateConversion.create({
        data: {
          affiliateSlug: ref,
          stripeSessionId: session.id,
          stripeCustomerId: session.customer as string,
          amountCents: session.amount_total,
          commissionCents: Math.floor(session.amount_total * 0.2), // 20% commission
          status: 'pending',
          convertedAt: new Date(),
        },
      });
    }
  }

  // Recurring commissions
  if (event.type === 'invoice.payment_succeeded') {
    const invoice = event.data.object as Stripe.Invoice;
    const sub = await stripe.subscriptions.retrieve(invoice.subscription as string);
    const ref = sub.metadata?.affiliate_ref;

    if (ref && invoice.amount_paid) {
      await db.affiliateCommission.create({
        data: {
          affiliateSlug: ref,
          stripeInvoiceId: invoice.id,
          amountCents: invoice.amount_paid,
          commissionCents: Math.floor(invoice.amount_paid * 0.2),
          type: 'recurring',
          status: 'pending',
          paidAt: new Date(),
        },
      });
    }
  }

  return NextResponse.json({ received: true });
}

Enter fullscreen mode Exit fullscreen mode

The Database Schema You Actually Own

This is what your data looks like when it lives in your own Postgres instance — queryable, JOIN-able, fully yours:

-- Affiliates
CREATE TABLE affiliates (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  slug            TEXT UNIQUE NOT NULL,        -- The ?ref= value
  name            TEXT NOT NULL,
  email           TEXT UNIQUE NOT NULL,
  commission_rate NUMERIC(5,4) DEFAULT 0.20,  -- 20%
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- One-time conversions
CREATE TABLE affiliate_conversions (
  id                   UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  affiliate_id         UUID NOT NULL REFERENCES affiliates(id),
  stripe_session_id    TEXT UNIQUE NOT NULL,
  stripe_customer_id   TEXT NOT NULL,
  amount_cents         INTEGER NOT NULL,
  commission_cents     INTEGER NOT NULL,
  status               TEXT NOT NULL DEFAULT 'pending', -- pending | paid | reversed
  converted_at         TIMESTAMPTZ NOT NULL,
  paid_at              TIMESTAMPTZ
);

-- Recurring commission events
CREATE TABLE affiliate_commissions (
  id               UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  affiliate_id     UUID NOT NULL REFERENCES affiliates(id),
  stripe_invoice_id TEXT UNIQUE NOT NULL,
  amount_cents     INTEGER NOT NULL,
  commission_cents INTEGER NOT NULL,
  type             TEXT NOT NULL DEFAULT 'recurring',
  status           TEXT NOT NULL DEFAULT 'pending',
  paid_at          TIMESTAMPTZ NOT NULL
);

CREATE INDEX idx_conversions_affiliate ON affiliate_conversions(affiliate_id);
CREATE INDEX idx_commissions_affiliate ON affiliate_commissions(affiliate_id);
CREATE INDEX idx_conversions_status ON affiliate_conversions(status);

Enter fullscreen mode Exit fullscreen mode

When your data is in a schema you control, cross-referencing affiliate performance against churn, LTV, or plan type is a SQL query, not a support ticket.


The Build-vs-Deploy Calculation

Here's the honest breakdown of what building affiliate tracking yourself actually costs:

Component DIY Effort Notes
Cookie capture logic ~1 hour Straightforward, shown above
Stripe checkout integration ~2 hours Shown above
Stripe webhook handler ~2 hours Shown above
Database schema + migrations ~1 hour Shown above
Affiliate-facing portal 80–150 hours The part you don't want to build
Payout management UI 40–80 hours Approve, reject, mark as paid
Email notifications 20–40 hours Commission approved, payout sent

The tracking code is 8 hours of work. The surrounding infrastructure — the portal your affiliates actually log into, the payout queue, the notification emails — is where real time disappears. That's not a reason to use a third-party platform. It's a reason to use open-source self-hosted infrastructure that ships all of it already built.


Where RefearnApp Comes In

RefearnApp is a free, open-source, self-hostable affiliate tracking platform built for exactly this stack — Next.js and Stripe — that gives you the 80–150 hours of infrastructure work without the 80–150 hours.

What it includes out of the box:

  • Affiliate portal — Your affiliates get a real dashboard to track their clicks, conversions, and commission history. You don't build this.
  • Commission engine — Configurable percentage and flat-rate commissions, with recurring attribution for subscription billing built in.
  • Payout tracking — Mark commissions as pending, approved, or paid. Full payout history per affiliate.
  • Zero revenue cut — No percentage rake on commissions. Every dollar goes to your affiliates.
  • Your database, your schema — When you deploy RefearnApp, the data is in your Postgres instance. You can query it, back it up, migrate it, or extend it whenever you want. No CSV exports. No support tickets. No lock-in.

The critical difference from third-party platforms: if you ever need to change tools, extend functionality, or migrate to a different payment processor, you're modifying TypeScript in a repo you own. Not starting over.


Summary

Third-party affiliate platforms don't just cost money. They cost optionality. The moment you decide to switch — and eventually you will — you discover that the real asset wasn't the dashboard or the payout UI. It was the tracking layer underneath: the referral links, the cookie logic, the attribution pipeline. All of it belongs to them.

Self-hosted affiliate tracking software means:

  • Your referral links are on your domain. They don't break when you change tools.
  • Your conversion data is in your database. You query it, you own it, you migrate it.
  • Your tracking logic is in your codebase. You read it, you modify it, you extend it.

- Switching costs drop to near zero. You update config, not rebuild a program.

Conclusion & Next Steps

Take Control of Your Affiliate Data

RefearnApp is fully open-source, self-hostable, and AGPL-3.0 licensed. You can deploy it completely free on your own infrastructure using Coolify or check out our direct paths: