SDKs
Typed client libraries for browser tracking (@affitor/sdk) and server-side conversion reporting (@affitor/sdk/server).
Beta — The @affitor/sdk package and CLI are in active development. The documented happy-path works; edge cases may change. Report issues on GitHub.
One package covers the full attribution chain via subpaths: the browser entry captures clicks and leads on the frontend; the /server entry reports conversions from your backend.
@affitor/sdk (Browser)
SSR-safe typed npm wrapper over the Affitor tracking endpoints. Captures ?aff= attribution, persists a affitor_click_id first-party cookie (60-day default), and reports click and lead events. All methods are no-ops on the server — safe to import in Next.js and other SSR frameworks without guards.
Install
npm install @affitor/sdkOr include the script tag directly:
<script
src="https://api.affitor.com/js/affitor-tracker.js"
data-affitor-program-id="YOUR_PROGRAM_ID"
defer
></script>Quick start
import { init, signup } from '@affitor/sdk';
// On app load (client-side only — no-op on server)
init({ programId: 123 });
// After the user completes signup / checkout
await signup('user_abc123', 'user@example.com');AffitorInitOptions
Options accepted by init() and the AffitorTracker constructor.
| Field | Type | Required | Description |
|---|---|---|---|
programId | number | string | No | Your affiliate program id. Falls back to data-affitor-program-id on the script tag, then window.AFFITOR_PROGRAM_ID. |
debug | boolean | No | Enables verbose console.log output. Errors always log regardless of this flag. |
cookieDomain | string | No | Force a cookie domain (e.g. .example.com). Auto-detected from the current hostname when omitted. |
apiBase | string | No | Override the tracking API base URL. Defaults to https://api.affitor.com. |
AffitorData
Returned by getData() — a snapshot of current tracker state.
| Field | Type | Description |
|---|---|---|
clickId | string | null | The active affitor_click_id (in-memory or from cookie). |
programId | number | null | The resolved program id. |
hasAttribution | boolean | true when a valid click has been tracked in this session. |
affiliateUrl | string | null | The affiliate URL that originated the current attribution. |
Methods
| Function | Signature | Description |
|---|---|---|
init | (options?: AffitorInitOptions) => AffitorTracker | null | Initialize tracking. Captures ?aff= from the current URL and sets up the click id cookie. Returns null on the server. |
signup | (customerKey: string, email?: string) => Promise<void> | Report a lead (signup) event. Pass your internal user id as customerKey. |
trackClick | (affiliateUrl?: string, existingClickId?: string | null) => Promise<void> | Manually track a click. Usually called automatically by init() when ?aff= is detected. |
getClickId | () => string | null | Return the current click id from memory or the affitor_click_id cookie. |
getData | () => AffitorData | null | Return a snapshot of tracker state. null before init() is called. |
getTracker | () => AffitorTracker | null | Return the underlying AffitorTracker instance (advanced use). null before init(). |
signup()requirescustomerKey— this is your own user id. Affitor uses it to bind the customer to a click so later backend sale calls can resolve attribution by id alone.
Full example (Next.js)
// app/providers.tsx — client component
'use client';
import { useEffect } from 'react';
import { init } from '@affitor/sdk';
export function AffitorInit() {
useEffect(() => { init({ programId: YOUR_PROGRAM_ID }); }, []);
return null;
}
// render <AffitorInit /> once in app/layout.tsxAfter successful signup, call signup(customerKey, email) from the SDK (or window.affitor.signup(...) for the script-tag path):
import { signup } from '@affitor/sdk';
// After successful signup
await signup(userId, email);@affitor/sdk/server (Server)
Typed, Bearer-authenticated client over the Affitor conversion API. Use this from your backend to report leads, sales, and refunds for any payment provider (Stripe, Polar, Lemon Squeezy, Paddle, etc.).
Never ship the program API key to the browser.
Install
npm install @affitor/sdkQuick start
import Affitor from '@affitor/sdk/server';
const affitor = new Affitor({ apiKey: process.env.AFFITOR_API_KEY! });
// On user signup — bind the customer to their affiliate click
await affitor.trackLead({ customerExternalId: user.id, clickId });
// On payment success — report the sale
await affitor.trackSale({
customerExternalId: user.id,
amount: 4999, // cents
invoiceId: inv.id, // idempotency key
});AffitorOptions
Constructor options for new Affitor(opts).
| Field | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Program API key (Bearer). Find it in your Affitor dashboard under program settings. |
apiUrl | string | No | Override the API base URL. Defaults to https://api.affitor.com. |
fetch | typeof fetch | No | Custom fetch implementation. Required for Node versions below 18 that lack a global fetch. |
AffitorResponse<T>
All methods return Promise<AffitorResponse<T>>.
| Field | Type | Description |
|---|---|---|
ok | boolean | true when the HTTP status was 2xx. |
status | number | HTTP status code. 0 on network error. |
data | T | null | Parsed response body when ok is true; null on error. |
error | string | undefined | Error message or HTTP status text when ok is false. |
Methods
trackLead(input: TrackLeadInput)
Binds customerExternalId to clickId. Once bound, later trackSale calls need only customerExternalId — no click id required.
At least one of customerExternalId or clickId is required; throws synchronously if both are missing.
| Field | Type | Required | Description |
|---|---|---|---|
customerExternalId | string | Conditional | Your own user id. Binds the customer to the click for downstream sale attribution. |
clickId | string | Conditional | affitor_click_id cookie value from the browser. |
email | string | No | Customer email (hashed server-side). |
eventName | string | No | Custom event label for segmentation. |
additionalData | Record<string, unknown> | No | Arbitrary metadata passed through to the event record. |
trackSale(input: TrackSaleInput)
Records a completed sale and creates a commission. Resolves attribution by customerExternalId first, then clickId. Idempotent by invoiceId.
At least one of customerExternalId or clickId is required, and amount must be a positive integer. Both amount and invoiceId throw synchronously if invalid.
| Field | Type | Required | Description |
|---|---|---|---|
customerExternalId | string | Conditional | Your own user id — resolves attribution from the earlier trackLead call. |
clickId | string | Conditional | affitor_click_id cookie value (fallback if no prior lead). |
amount | number | Yes | Sale amount in integer cents (e.g. 4999 = $49.99). |
invoiceId | string | Yes | Unique invoice or transaction id — used as an idempotency key to deduplicate retries. |
currency | string | No | ISO 4217 currency code. Defaults to USD. |
saleType | 'payment' | 'subscription' | No | Categorize the sale type. |
isRecurring | boolean | No | true for recurring subscription charges. |
subscriptionId | string | No | External subscription id from your payment provider. |
subscriptionInterval | 'monthly' | 'quarterly' | 'annual' | No | Billing cadence for subscription sales. |
eventName | string | No | Custom event label for segmentation. |
trackClick(input?: TrackClickInput)
Track a click server-side. Click tracking is usually handled in the browser by @affitor/sdk; use this only for server-rendered flows or redirect-based affiliate links.
This endpoint does not require authentication.
| Field | Type | Required | Description |
|---|---|---|---|
affiliateUrl | string | No | The full affiliate URL containing the ?aff= parameter. |
pageUrl | string | No | The landing page URL. |
referrerUrl | string | No | The HTTP referrer. |
existingClickId | string | No | Pass an existing click id to enable last-partner attribution (overwrites old click). |
trackRefund(input: TrackRefundInput)
Reverses the commission for a previously tracked sale. Call from your payment provider's refund webhook. Idempotent by invoiceId.
- Omit
refundAmountCents(or pass0) for a full refund → commission status set toreversed. - Pass a partial amount → commission status set to
refunded(proportional).
invoiceId is required and throws synchronously if missing.
| Field | Type | Required | Description |
|---|---|---|---|
invoiceId | string | Yes | The invoiceId you passed to trackSale — identifies which sale to refund. |
refundAmountCents | number | No | Refund amount in integer cents. Omit or pass 0 for a full refund. |
refundReason | string | No | Optional reason string passed through to the refund record. |
Full example (Stripe webhook)
import Stripe from 'stripe';
import Affitor from '@affitor/sdk/server';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const affitor = new Affitor({ apiKey: process.env.AFFITOR_API_KEY! });
// POST /webhooks/stripe
export async function handleStripeWebhook(rawBody: Buffer, sig: string) {
const event = stripe.webhooks.constructEvent(
rawBody,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
if (event.type === 'checkout.session.completed') {
const session = event.data.object as Stripe.Checkout.Session;
const result = await affitor.trackSale({
customerExternalId: session.client_reference_id ?? undefined,
amount: session.amount_total ?? 0,
invoiceId: session.payment_intent as string,
currency: session.currency?.toUpperCase(),
saleType: 'payment',
});
if (!result.ok) {
console.error('Affitor trackSale failed:', result.error);
}
}
if (event.type === 'charge.refunded') {
const charge = event.data.object as Stripe.Charge;
await affitor.trackRefund({
invoiceId: charge.payment_intent as string,
refundAmountCents: charge.amount_refunded,
});
}
}Related
- Track Click — raw HTTP endpoint reference
- Track Lead — raw HTTP endpoint reference
- Track Sale — raw HTTP endpoint reference
- Track Refund — raw HTTP endpoint reference
- Lead Tracking Guide — implementation walkthrough