Next.js + NextAuth
Add Affitor lead tracking to a Next.js app using NextAuth (Auth.js) — handle the click-id cookie gap correctly.
This guide covers the signup step when your Next.js app uses NextAuth (Auth.js) for authentication. Steps 1 (capture click) and 3 (track sale) are identical to the base Next.js guide — this page focuses on what is different: wiring the lead call to NextAuth's auth lifecycle without losing the click attribution.
:::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
- A Next.js App Router app with NextAuth (Auth.js) configured
1. Capture the click
Follow Step 1 of the Next.js guide. Install @affitor/sdk, create an AffitorInit client component, and render it in your root layout. No changes needed for NextAuth.
2. Track the signup
The cookie gap
NextAuth's events.createUser callback fires on the server, once per new user — it looks like the ideal place to call trackLead. It is — but with one important caveat:
The affitor_click_id is a first-party browser cookie. A server-side callback has no access to it. If you call @affitor/sdk/server trackLead from events.createUser without passing a clickId, the lead is recorded but has no partner to attribute to — the click attribution is lost.
Recommended: track client-side after sign-in
The most reliable path is to fire signup() from the browser, immediately after NextAuth confirms a new user session. The browser SDK reads the affitor_click_id cookie automatically — no forwarding required.
// app/auth-tracker.tsx
'use client';
import { useEffect, useRef } from 'react';
import { useSession } from 'next-auth/react';
import { signup } from '@affitor/sdk';
export function AffitorAuthTracker() {
const { data: session, status } = useSession();
const tracked = useRef(false);
useEffect(() => {
if (status !== 'authenticated' || tracked.current) return;
if (!session?.user?.id) return;
// Fire once per new session. Your backend should also guard
// against duplicate leads using customerExternalId.
tracked.current = true;
signup(session.user.id, session.user.email ?? undefined);
}, [status, session]);
return null;
}Render it inside your root layout, alongside AffitorInit:
// app/layout.tsx
import { AffitorInit } from './affitor-init';
import { AffitorAuthTracker } from './auth-tracker';
import { SessionProvider } from 'next-auth/react';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<SessionProvider>
<AffitorInit />
<AffitorAuthTracker />
{children}
</SessionProvider>
</body>
</html>
);
}:::note
signup() calls POST /api/v1/track/lead. Affitor deduplicates by customerExternalId, so calling it on every sign-in is safe — only the first call for a given ID creates a lead.
:::
Alternative: server-side via events.createUser with click-id forwarding
If you need the lead recorded the moment the user row is created (before the client redirects), you can use events.createUser — but you must forward the click ID from the browser yourself.
Step A — read the click ID on the client before the user submits the sign-up form:
'use client';
import { getClickId } from '@affitor/sdk';
// In your sign-up form handler, read the click ID and include
// it in the request or store it on the user's session/metadata.
const clickId = getClickId(); // returns the affitor_click_id cookie valueStep B — pass it through to your NextAuth config and call trackLead in the event:
// auth.ts (NextAuth config)
import Affitor from '@affitor/sdk/server';
import type { NextAuthConfig } from 'next-auth';
const affitor = new Affitor({ apiKey: process.env.AFFITOR_API_KEY! });
export const authConfig: NextAuthConfig = {
// ...providers, callbacks, etc.
events: {
async createUser({ user }) {
// user.id is the NextAuth-generated user ID — use it as
// customerExternalId everywhere: signup, sale, Stripe metadata.
await affitor.trackLead({
customerExternalId: user.id,
clickId: user.affitorClickId ?? undefined, // forwarded from the browser
email: user.email ?? undefined,
});
},
},
};:::note
user.affitorClickId is illustrative — the forwarding mechanism depends on your auth flow. Common approaches: store the click ID in the sign-up form's hidden field and write it to a custom user attribute before createUser fires, or pass it through a custom session object. Without it, clickId is undefined and the lead will not be attributed to a partner.
:::
If you cannot reliably forward the click ID in your server webhook, prefer the client-side path above.
3. Track the sale
Follow Step 3 of the Next.js guide. Use session.user.id as affitor_customer_key (Stripe path) or customerExternalId (server SDK path) — it must match the value you sent at signup.
Verify
- Visit your site with `?aff=TESTCODE` — an `affitor_click_id` cookie is set
- `signup()` fires `POST /api/v1/track/lead` with a 2xx response after sign-in
- The lead appears under your program's tracking events, attributed to the correct partner
- If leads appear but show no partner, the click ID is not being read — confirm `AffitorInit` runs before the user signs in. Send `additional_data: { test_mode: true }` to create test events without commissions.
Common mistakes
- Calling trackLead from events.createUser without a clickId — the lead is recorded but has no partner attribution. Use the client-side signup() path or forward the click ID explicitly.
- Different customer IDs at signup vs sale — `session.user.id` must be used consistently as `customerExternalId` / `affitor_customer_key` everywhere.
- Wrapping AffitorAuthTracker outside SessionProvider — useSession() will throw. SessionProvider must be an ancestor.
- Calling signup() before init() runs — AffitorInit must mount first so the SDK is initialized before the auth tracker fires.
- Trying to track a sale from the browser — sales are server-side only (Stripe metadata or the server SDK).