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.

Info

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.

  1. 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 },
  },
});
  1. 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

Verify success
Browser
  • Visit your site with ?aff=TESTCODE — an affitor_click_id cookie is set
Network
  • Your Edge Function's trackLead() call to POST /api/v1/track/lead returns 2xx without provisional: true
Dashboard
  • The click, lead, and sale appear under your program's tracking events
If it doesn't work
  • Create a test lead from your server: affitor.trackLead({ customerExternalId: 'test_customer', additionalData: { test_mode: true, program_id: YOUR_PROGRAM_ID } })

Common mistakes

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.id must match the customerExternalId / affitor_customer_key sent 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).
Next recommended step
Ready to track sales?

Connect Stripe so Affitor attributes subscription renewals automatically.

Continue →
© 2026 Affitor