---
id: "api-reference/integrations/nextjs-supabase"
type: "doc"
url: "https://affitor.com/docs/api-reference/integrations/nextjs-supabase"
---
# 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](/docs/api-reference/integrations/nextjs) — refer there for full detail.

:::note

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](/docs/api-reference/integrations/nextjs#1-capture-the-click). 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](/docs/api-reference/track-lead/#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

1. On the client, read the click ID and store it on the user's metadata during sign-up:

```tsx
'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 },
  },
});
```

2. 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`:

```ts
// 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](/docs/api-reference/integrations/nextjs#3-track-the-sale). 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

<VerifySuccess
  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"]}
  ifNotWorking={["Create a test lead from your server: `affitor.trackLead({ customerExternalId: 'test_customer', additionalData: { test_mode: true, program_id: YOUR_PROGRAM_ID } })`"]}
/>

## Common mistakes

<CommonMistakes items={[
  "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)."
]} />

<NextStep title="Ready to track sales?" description="Connect Stripe so Affitor attributes subscription renewals automatically." href="/docs/api-reference/integrations/stripe" />
