Next.js + Supabase Auth
Add Affitor tracking to a Next.js app that uses Supabase Auth — clicks, signups, and sales.
This guide covers the Supabase-specific signup step. Steps 1 (capture the click) and 3 (track the sale) are identical to the base Next.js guide — refer there for full detail.
The @affitor/sdk package is Beta. The documented happy-path works; report issues on GitHub.
Prerequisites
- Your program ID (dashboard → program settings)
- A program API key for server-side calls (leads and sales)
- A Next.js app with Supabase Auth configured
1. Capture the click
Follow Step 1 of the Next.js guide. Install @affitor/sdk, create the client component that wraps your app in <AffitorProvider programId="…">, and render it once in your root layout.
2. Track the signup
Send the lead from your server with your program API key, which is what settles it: new programs require server-verified leads (see Lead auth policy).
The affitor_click_id is a first-party browser cookie. A Supabase Edge Function or DB trigger runs with no access to that cookie, so you must forward the click ID yourself.
Recommended: Edge Function with a forwarded click ID
- On the client, read the click ID and store it on the user's metadata during sign-up:
'use client';
import { useAffitor } from '@affitor/sdk/react';
// inside your sign-up form component
const affitor = useAffitor(); // null until the tracker has loaded
// during your sign-up form submit (disable the button until `affitor` is set)
const clickId = affitor?.getClickId() ?? null; // the affitor_click_id cookie value
await supabase.auth.signUp({
email,
password,
options: {
data: { affitor_click_id: clickId },
},
});- In your Edge Function (for example, one you call after sign-up, or one fired by a database webhook on new users), read the forwarded click ID and call
@affitor/sdk/server:
// supabase/functions/on-signup/index.ts
import Affitor from 'npm:@affitor/sdk/server';
const affitor = new Affitor({ apiKey: Deno.env.get('AFFITOR_API_KEY')! });
// `user` is the new Supabase user, from your request or webhook payload
const clickId = user.user_metadata?.affitor_click_id;
if (clickId) {
await affitor.trackLead({
customerExternalId: user.id, // Supabase user.id
clickId,
email: user.email,
});
}user.id is the stable Supabase UUID. Use this exact value as customerExternalId everywhere: at sale time and in any Stripe metadata.
Without a forwarded clickId, Affitor cannot find the click: the lead call returns 400 and nothing is recorded. That is expected for users who did not arrive through a partner link.
Browser signup() (provisional on new programs)
The tracker also has a browser helper, affitor.signup(userId, email), which you can call from Supabase's client-side onAuthStateChange listener. It reads the cookie automatically, but it sends the lead without your API key. On programs that require server-verified leads (the default for new programs), that lead is stored as provisional: it does not settle, does not save the user ID, and does not count or pay per lead until your server sends the lead or a Stripe Checkout payment for that customer arrives through your connected Stripe account. A sale sent to /api/v1/track/sale does not settle it. Use it alongside the Edge Function path, not instead of it.
3. Track the sale
Follow Step 3 of the Next.js guide. Use session.user.id (your customerExternalId) as affitor_customer_key in Stripe metadata, or as customerExternalId in affitor.trackSale(). Sales must always be tracked server-side.
Verify
- Visit your site with
?aff=TESTCODE— anaffitor_click_idcookie is set
- Your Edge Function's
trackLead()call toPOST /api/v1/track/leadreturns 2xx withoutprovisional: true
- The click, lead, and sale appear under your program's tracking events
- Create a test lead from your server:
affitor.trackLead({ customerExternalId: 'test_customer', additionalData: { test_mode: true, program_id: YOUR_PROGRAM_ID } })
Common mistakes
- Relying on the browser signup() alone — on new programs it stays provisional until your server sends the lead.
- Using different IDs at signup vs sale —
session.user.idmust match thecustomerExternalId/affitor_customer_keysent at sale time. - Tracking leads from a server webhook without forwarding the click ID — the browser cookie is not available server-side, so the lead call returns 400 and nothing is recorded. Store and forward the click ID explicitly.
- Subscriptions missing
subscription_data.metadata— renewals won't attribute to the originating partner click. - Trying to track a sale from the browser — sales are server-side only (Stripe metadata or the server SDK).