Skip to main content
RapidDev - Software Development Agency

How to Build Affiliate tracking app with V0

Build an affiliate tracking app with V0 using Next.js, Supabase, and Stripe. You'll get click-through attribution, real-time conversion tracking, tiered commission calculations, and an affiliate dashboard with automated payout management — all in about 1-2 hours without any local setup.

What you'll build

  • Click tracking API route that records referral link visits with IP, user agent, and landing page data
  • Cookie-based click-to-conversion attribution system using first-party signed cookies
  • Affiliate dashboard with shadcn/ui Cards for earnings summary and AreaChart for click/conversion trends
  • Admin panel for managing affiliates, viewing conversions, and approving payouts
  • Conversion webhook endpoint that receives Stripe events and attributes sales to affiliates
  • Referral link generator with copy-to-clipboard functionality using shadcn/ui Input and Button
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build an affiliate tracking app with V0 using Next.js, Supabase, and Stripe. You'll get click-through attribution, real-time conversion tracking, tiered commission calculations, and an affiliate dashboard with automated payout management — all in about 1-2 hours without any local setup.

What you're building

Affiliate marketing is one of the most cost-effective growth channels for SaaS and e-commerce businesses. Instead of paying for ads upfront, you only pay commissions when affiliates actually drive sales. But tracking those referrals accurately is the hard part.

V0 makes building this system dramatically faster. You describe the tracking logic and dashboard layout in natural language, and V0 generates the Next.js API routes, Supabase queries, and React components. Connect Supabase via the Connect panel for instant database provisioning and Stripe via Vercel Marketplace for payment event tracking.

The architecture uses Next.js API routes for the click-tracking redirect and conversion webhook, Server Components for the affiliate and admin dashboards, and Supabase for storing affiliates, clicks, conversions, and payouts. Stripe webhooks notify your app when purchases complete so commissions are attributed automatically.

Final result

A complete affiliate tracking system with referral link generation, click recording, cookie-based conversion attribution, automatic commission calculation, and dashboards for both affiliates and administrators.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
StripePayments
RechartsData Visualization

Prerequisites

  • A V0 account (Premium plan recommended for multi-feature builds)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • A Stripe account with webhook access (test mode works for development)
  • An existing product or checkout flow where you want to track affiliate referrals

Build steps

1

Set up the database schema for affiliate tracking

Start a new V0 project and connect Supabase via the Connect panel. Then prompt V0 to create the tables for affiliates, clicks, conversions, and payouts. The conversion table includes a generated column that auto-calculates commission based on the affiliate's rate.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an affiliate tracking system with Supabase. Create these tables:
3// 1. affiliates: id (uuid PK), user_id (uuid FK to auth.users), code (text unique), commission_rate (numeric default 0.10), status (text default 'active'), created_at (timestamptz)
4// 2. clicks: id (uuid PK), affiliate_id (uuid FK), ip_address (inet), user_agent (text), referrer_url (text), landing_page (text), created_at (timestamptz)
5// 3. conversions: id (uuid PK), click_id (uuid FK), affiliate_id (uuid FK), order_id (text unique), amount (numeric), commission (numeric), status (text default 'pending'), created_at (timestamptz)
6// 4. payouts: id (uuid PK), affiliate_id (uuid FK), amount (numeric), period_start (date), period_end (date), status (text default 'pending'), paid_at (timestamptz)
7// Add RLS policies so affiliates can only see their own data.
8// Generate the SQL migration.

Pro tip: Use the Connect panel to add Supabase in two clicks — it auto-provisions NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in the Vars tab.

Expected result: Supabase is connected and all four tables are created with RLS policies. The affiliates table has a unique code column for referral links.

2

Build the click tracking API route with cookie attribution

Create an API route that records affiliate clicks and sets a first-party cookie with the affiliate code. When someone clicks a referral link like /api/track?ref=ABC123, it logs the click and redirects to the landing page with the cookie set for 30-day attribution.

app/api/track/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9export async function GET(req: NextRequest) {
10 const { searchParams } = new URL(req.url)
11 const refCode = searchParams.get('ref')
12 const landingPage = searchParams.get('url') || '/'
13
14 if (!refCode) {
15 return NextResponse.redirect(new URL(landingPage, req.url))
16 }
17
18 const { data: affiliate } = await supabase
19 .from('affiliates')
20 .select('id')
21 .eq('code', refCode)
22 .eq('status', 'active')
23 .single()
24
25 if (affiliate) {
26 await supabase.from('clicks').insert({
27 affiliate_id: affiliate.id,
28 ip_address: req.headers.get('x-forwarded-for') || 'unknown',
29 user_agent: req.headers.get('user-agent') || '',
30 referrer_url: req.headers.get('referer') || '',
31 landing_page: landingPage,
32 })
33 }
34
35 const response = NextResponse.redirect(new URL(landingPage, req.url))
36 response.cookies.set('affiliate_ref', refCode, {
37 httpOnly: true,
38 secure: true,
39 sameSite: 'lax',
40 maxAge: 30 * 24 * 60 * 60,
41 path: '/',
42 })
43
44 return response
45}

Expected result: Visiting /api/track?ref=ABC123&url=/pricing logs a click in Supabase, sets a 30-day cookie, and redirects to /pricing.

3

Create the Stripe webhook for conversion attribution

Build the webhook handler that listens for Stripe checkout.session.completed events. It reads the affiliate cookie from the checkout metadata, attributes the sale to the correct affiliate, and calculates the commission automatically.

app/api/webhooks/stripe/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import Stripe from 'stripe'
3import { createClient } from '@supabase/supabase-js'
4
5const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export async function POST(req: NextRequest) {
12 const body = await req.text()
13 const sig = req.headers.get('stripe-signature')!
14
15 let event: Stripe.Event
16 try {
17 event = stripe.webhooks.constructEvent(
18 body,
19 sig,
20 process.env.STRIPE_WEBHOOK_SECRET!
21 )
22 } catch {
23 return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
24 }
25
26 if (event.type === 'checkout.session.completed') {
27 const session = event.data.object as Stripe.Checkout.Session
28 const refCode = session.metadata?.affiliate_ref
29
30 if (refCode) {
31 const { data: affiliate } = await supabase
32 .from('affiliates')
33 .select('id, commission_rate')
34 .eq('code', refCode)
35 .single()
36
37 if (affiliate && session.amount_total) {
38 const amount = session.amount_total / 100
39 await supabase.from('conversions').upsert({
40 affiliate_id: affiliate.id,
41 order_id: session.id,
42 amount,
43 commission: amount * affiliate.commission_rate,
44 status: 'confirmed',
45 }, { onConflict: 'order_id' })
46 }
47 }
48 }
49
50 return NextResponse.json({ received: true })
51}

Pro tip: Always use request.text() instead of request.json() for Stripe webhooks — Stripe signature verification requires the raw request body, and parsing it as JSON first corrupts the signature check.

Expected result: When a Stripe checkout completes with affiliate metadata, a conversion row is created in Supabase with the correct commission amount.

4

Build the affiliate dashboard with earnings and link management

Create the affiliate-facing dashboard where they can view their referral link, see click and conversion stats, and track earnings over time. Use Server Components for data fetching and Recharts for the trends chart.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an affiliate dashboard at app/dashboard/page.tsx.
3// Requirements:
4// - Show the affiliate's referral link in a shadcn/ui Input with a copy-to-clipboard Button
5// - Display summary Cards: Total Clicks, Total Conversions, Conversion Rate, Total Earnings, Pending Payout
6// - Add an AreaChart (Recharts) showing clicks and conversions per day for the last 30 days
7// - Show a Table of recent conversions with columns: Date, Order ID, Amount, Commission, Status Badge
8// - Add Tabs to switch between Overview, Conversions, and Payouts views
9// - Payouts tab shows a Table of payout history with status Badge (pending/paid)
10// - Use Server Components for data fetching from Supabase
11// - The referral link format should be: {origin}/api/track?ref={code}

Expected result: The dashboard shows the affiliate's referral link, summary stats in Cards, a 30-day trend chart, and tables for conversions and payouts.

5

Create the admin view for managing affiliates and approving payouts

Build an admin page where you can view all affiliates, their performance metrics, and approve pending payouts. This uses Server Actions for payout approval with proper authorization checks.

app/admin/actions.ts
1'use server'
2
3import { createClient } from '@supabase/supabase-js'
4import { auth } from '@clerk/nextjs/server'
5import { revalidatePath } from 'next/cache'
6
7const supabase = createClient(
8 process.env.SUPABASE_URL!,
9 process.env.SUPABASE_SERVICE_ROLE_KEY!
10)
11
12export async function approvePayout(payoutId: string) {
13 const { userId } = await auth()
14 if (!userId) throw new Error('Unauthorized')
15
16 const { error } = await supabase
17 .from('payouts')
18 .update({
19 status: 'paid',
20 paid_at: new Date().toISOString(),
21 })
22 .eq('id', payoutId)
23 .eq('status', 'pending')
24
25 if (error) throw new Error(error.message)
26 revalidatePath('/admin/affiliates')
27}
28
29export async function generatePayout(affiliateId: string, periodStart: string, periodEnd: string) {
30 const { data: conversions } = await supabase
31 .from('conversions')
32 .select('commission')
33 .eq('affiliate_id', affiliateId)
34 .eq('status', 'confirmed')
35 .gte('created_at', periodStart)
36 .lte('created_at', periodEnd)
37
38 const totalCommission = conversions?.reduce((sum, c) => sum + c.commission, 0) ?? 0
39
40 if (totalCommission > 0) {
41 await supabase.from('payouts').insert({
42 affiliate_id: affiliateId,
43 amount: totalCommission,
44 period_start: periodStart,
45 period_end: periodEnd,
46 status: 'pending',
47 })
48 }
49
50 revalidatePath('/admin/affiliates')
51}

Expected result: Admins can view all affiliates and their metrics. Payouts can be generated for a date range and approved with a single click.

Complete code

app/api/track/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9export async function GET(req: NextRequest) {
10 const { searchParams } = new URL(req.url)
11 const refCode = searchParams.get('ref')
12 const landingPage = searchParams.get('url') || '/'
13
14 if (!refCode) {
15 return NextResponse.redirect(new URL(landingPage, req.url))
16 }
17
18 const { data: affiliate } = await supabase
19 .from('affiliates')
20 .select('id')
21 .eq('code', refCode)
22 .eq('status', 'active')
23 .single()
24
25 if (affiliate) {
26 await supabase.from('clicks').insert({
27 affiliate_id: affiliate.id,
28 ip_address: req.headers.get('x-forwarded-for') || 'unknown',
29 user_agent: req.headers.get('user-agent') || '',
30 referrer_url: req.headers.get('referer') || '',
31 landing_page: landingPage,
32 })
33 }
34
35 const response = NextResponse.redirect(new URL(landingPage, req.url))
36 response.cookies.set('affiliate_ref', refCode, {
37 httpOnly: true,
38 secure: true,
39 sameSite: 'lax',
40 maxAge: 30 * 24 * 60 * 60,
41 path: '/',
42 })
43
44 return response
45}

Customization ideas

Add multi-tier commission rates

Implement tiered commissions where affiliates earn higher rates as they drive more sales (e.g., 10% for 0-10 sales, 15% for 11-50, 20% for 50+) using a Supabase function that checks lifetime conversions.

Add referral link customization

Let affiliates create vanity codes like /ref/john instead of random codes by adding a custom code input in the dashboard with uniqueness validation.

Add email notifications for conversions

Send affiliates an email notification when a new conversion is recorded by calling the Resend API from the Stripe webhook handler after inserting the conversion.

Add fraud detection

Implement basic click fraud detection by checking for duplicate IP addresses within a short time window and flagging suspicious activity patterns in a Supabase function.

Common pitfalls

Pitfall: Using third-party cookies for affiliate attribution

How to avoid: Use first-party cookies set from your own domain's API route. The /api/track route sets a first-party cookie that persists for 30 days and is readable during checkout.

Pitfall: Not preventing duplicate conversion records

How to avoid: Add a unique constraint on the order_id column in the conversions table and use ON CONFLICT DO NOTHING or upsert when inserting conversions from the webhook.

Pitfall: Exposing STRIPE_WEBHOOK_SECRET with NEXT_PUBLIC_ prefix

How to avoid: Store STRIPE_WEBHOOK_SECRET in V0's Vars tab without any prefix. It should only be accessible in the server-side webhook API route.

Best practices

  • Use first-party cookies for attribution since third-party cookies are blocked by most modern browsers
  • Always verify Stripe webhook signatures using request.text() for the raw body — never request.json()
  • Store all secret keys (STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, SUPABASE_SERVICE_ROLE_KEY) in V0's Vars tab without NEXT_PUBLIC_ prefix
  • Use Supabase RLS policies so affiliates can only view their own clicks, conversions, and payouts
  • Add unique constraints on order_id to prevent duplicate conversion records from webhook retries
  • Use Server Components for the dashboard pages to keep database queries server-side and avoid exposing business logic
  • Use Design Mode (Option+D) to adjust dashboard card layouts, chart colors, and table styling without spending credits
  • Set the affiliate cookie with httpOnly and secure flags to prevent client-side JavaScript from reading or tampering with it

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building an affiliate tracking app with Next.js App Router, Supabase, and Stripe. I need click tracking with cookie-based attribution, a Stripe webhook to record conversions, and an affiliate dashboard showing earnings. Help me design the Supabase schema and the click-to-conversion attribution flow using first-party cookies.

Build Prompt

Build a conversion attribution system where the checkout flow reads the affiliate_ref cookie and passes it as Stripe Checkout Session metadata. The webhook handler should look up the affiliate by code, calculate commission based on their rate, and upsert the conversion with ON CONFLICT on order_id to prevent duplicates. Use Supabase and handle Stripe signature verification with request.text().

Frequently asked questions

How does cookie-based affiliate attribution work?

When someone clicks an affiliate link, the /api/track route sets a first-party cookie with the affiliate code. This cookie persists for 30 days. When the user completes a purchase, the checkout flow reads this cookie and passes it as metadata to Stripe, which then includes it in the webhook event for conversion attribution.

Can I track affiliate conversions without Stripe?

Yes. Instead of using a Stripe webhook, you can call the conversion API route directly from your order confirmation logic. The key is to read the affiliate cookie at the point of conversion and attribute it to the correct affiliate in Supabase.

What V0 plan do I need for an affiliate tracking app?

V0 Premium is recommended because the affiliate system requires multiple pages and API routes. The free tier gives limited credits which may not cover the full build. Premium provides enough credits for the tracking routes, dashboards, and admin pages.

How do I prevent click fraud from affiliates?

Implement IP-based deduplication by adding a unique constraint on (affiliate_id, ip_address, date) in the clicks table. You can also add rate limiting per affiliate code and flag accounts with abnormally high click-to-conversion ratios for manual review.

How do I deploy the affiliate tracking app?

Click Share then Publish to Production in V0 for instant Vercel deployment. After publishing, copy your production URL and register the Stripe webhook endpoint at https://yourdomain.vercel.app/api/webhooks/stripe in the Stripe Dashboard.

Can I add PayPal as an alternative to Stripe?

Yes. Add a PayPal webhook handler at a separate API route that listens for PayPal IPN (Instant Payment Notification) events and follows the same conversion attribution logic — read the affiliate code from the order metadata and insert a conversion row.

Can RapidDev help build a custom affiliate tracking system?

Yes. RapidDev has built 600+ apps including complex affiliate and referral systems with multi-tier commissions, fraud detection, and automated payouts. Book a free consultation to discuss your specific tracking requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.