Next.js
Add Affitor tracking to a Next.js (App Router) app — clicks, signups, and sales.
This guide wires Affitor into a Next.js App Router app with the three moves every integration needs: capture the click, track the signup, track the sale.
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 on the App Router
1. Capture the click
Install the SDK and load the tracker once on the client. The tracker reads ?aff= from the URL and stores a first-party affitor_click_id cookie. AffitorProvider (from @affitor/sdk/react) loads it in the browser; on the server it does nothing and useAffitor() returns null.
npm i @affitor/sdkCreate a client component that wraps your app in the provider:
// app/affitor-provider.tsx
'use client';
import { AffitorProvider } from '@affitor/sdk/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <AffitorProvider programId="YOUR_PROGRAM_ID">{children}</AffitorProvider>;
}Render it once in your root layout:
// app/layout.tsx
import { Providers } from './affitor-provider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}2. Track the signup
When a user finishes signing up, link them to their click from your server, with your own stable user ID — you'll reuse this exact value at sale time. Send it with the server SDK and your program API key (e.g. in a route handler or right after the DB write):
// server only — e.g. app/api/signup/route.ts
import { cookies } from 'next/headers';
import Affitor from '@affitor/sdk/server';
const affitor = new Affitor({ apiKey: process.env.AFFITOR_API_KEY! });
// after the user row is created
const clickId = (await cookies()).get('affitor_click_id')?.value;
if (clickId) {
await affitor.trackLead({
customerExternalId: user.id, // same ID you'll send at sale time
clickId, // affitor_click_id, forwarded from the cookie
email: user.email, // optional
});
}A lead needs the click: without a clickId (or a customerExternalId Affitor already knows), the API returns 400 and records nothing, which is expected for visitors who did not come through a partner link.
The tracker also has a browser helper, affitor.signup(user.id, user.email) (from useAffitor() in @affitor/sdk/react). New programs require server-verified leads, so a browser-only signup is stored as provisional: the API returns 200 with provisional: true, but the lead does not settle, does not save your user ID, and does not count or pay per lead until your server sends the lead above 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. See Lead auth policy.
3. Track the sale
Pick one of two paths — this is the real choice: does Stripe tell us, or does your server?
Option A — Stripe integration (recommended for Stripe Checkout)
Attach Affitor metadata when you create the Checkout Session in your own Stripe account. Affitor reads your Stripe webhooks and attributes the sale automatically — no extra call.
// server — creating the Checkout Session
const session = await stripe.checkout.sessions.create({
mode: 'subscription', // or 'payment'
line_items: [{ price: 'price_xxx', quantity: 1 }],
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
metadata: {
affitor_click_id: clickId, // from the affitor_click_id cookie
affitor_customer_key: user.id, // SAME id you used at signup
program_id: 'YOUR_PROGRAM_ID',
},
// subscriptions: duplicate the SAME metadata so renewals attribute
subscription_data: {
metadata: {
affitor_click_id: clickId,
affitor_customer_key: user.id,
program_id: 'YOUR_PROGRAM_ID',
},
},
});Option B — Server-side tracking (any provider)
Report the finalized sale from your backend. Works with any payment provider.
import Affitor from '@affitor/sdk/server';
const affitor = new Affitor({ apiKey: process.env.AFFITOR_API_KEY! });
await affitor.trackSale({
customerExternalId: user.id, // SAME id as signup
amount: 4999, // integer cents
invoiceId: invoice.id, // idempotency key — a duplicate returns 409
});Verify
- Visit your site with
?aff=TESTCODE— anaffitor_click_idcookie is set
- Your server'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 } }). For a test sale, callPOST /api/v1/track/saledirectly with your API key andadditional_data: { test_mode: true }— the SDK'strackSale()cannot send it
Common mistakes
- Different customer IDs at signup vs sale —
affitor_customer_keymust equal thecustomer_key/customerExternalIdyou sent at signup. - Subscriptions missing
subscription_data.metadata— renewals won't attribute. - Loading the tracker on the server —
AffitorProviderandloadAffitor()must run in a client component (on the server they resolve tonull). - Relying on the browser
signup()alone — on new programs it stays provisional until your server sends the lead. - Trying to track a sale from the browser — sales are server-side only (Stripe metadata or the server SDK).