Integrations
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 (program menu → Program settings → Business)
- 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). Every program requires server-verified leads unless it is set to browser_allowed, 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)
Connect your Stripe account under Program settings › Payments, then attach Affitor metadata when you create the Checkout Session in your own Stripe account. Affitor receives the account's events through Stripe Connect (you add no webhook) and attributes the sale automatically, with 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: the SAME metadata here reaches the subscription's invoices
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. The click never reaches the subscription's invoices, so its payments are attributed only if the buyer's email or Stripe customer ID matches a customer Affitor already has. - 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 — by default 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).