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/sdk

Or 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.

FieldTypeRequiredDescription
programIdnumber | stringNoYour affiliate program id. Falls back to data-affitor-program-id on the script tag, then window.AFFITOR_PROGRAM_ID.
debugbooleanNoEnables verbose console.log output. Errors always log regardless of this flag.
cookieDomainstringNoForce a cookie domain (e.g. .example.com). Auto-detected from the current hostname when omitted.
apiBasestringNoOverride the tracking API base URL. Defaults to https://api.affitor.com.

AffitorData

Returned by getData() — a snapshot of current tracker state.

FieldTypeDescription
clickIdstring | nullThe active affitor_click_id (in-memory or from cookie).
programIdnumber | nullThe resolved program id.
hasAttributionbooleantrue when a valid click has been tracked in this session.
affiliateUrlstring | nullThe affiliate URL that originated the current attribution.

Methods

FunctionSignatureDescription
init(options?: AffitorInitOptions) => AffitorTracker | nullInitialize 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 | nullReturn the current click id from memory or the affitor_click_id cookie.
getData() => AffitorData | nullReturn a snapshot of tracker state. null before init() is called.
getTracker() => AffitorTracker | nullReturn the underlying AffitorTracker instance (advanced use). null before init().

signup() requires customerKey — 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.tsx

After 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/sdk

Quick 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).

FieldTypeRequiredDescription
apiKeystringYesProgram API key (Bearer). Find it in your Affitor dashboard under program settings.
apiUrlstringNoOverride the API base URL. Defaults to https://api.affitor.com.
fetchtypeof fetchNoCustom fetch implementation. Required for Node versions below 18 that lack a global fetch.

AffitorResponse<T>

All methods return Promise<AffitorResponse<T>>.

FieldTypeDescription
okbooleantrue when the HTTP status was 2xx.
statusnumberHTTP status code. 0 on network error.
dataT | nullParsed response body when ok is true; null on error.
errorstring | undefinedError 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.

FieldTypeRequiredDescription
customerExternalIdstringConditionalYour own user id. Binds the customer to the click for downstream sale attribution.
clickIdstringConditionalaffitor_click_id cookie value from the browser.
emailstringNoCustomer email (hashed server-side).
eventNamestringNoCustom event label for segmentation.
additionalDataRecord<string, unknown>NoArbitrary 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.

FieldTypeRequiredDescription
customerExternalIdstringConditionalYour own user id — resolves attribution from the earlier trackLead call.
clickIdstringConditionalaffitor_click_id cookie value (fallback if no prior lead).
amountnumberYesSale amount in integer cents (e.g. 4999 = $49.99).
invoiceIdstringYesUnique invoice or transaction id — used as an idempotency key to deduplicate retries.
currencystringNoISO 4217 currency code. Defaults to USD.
saleType'payment' | 'subscription'NoCategorize the sale type.
isRecurringbooleanNotrue for recurring subscription charges.
subscriptionIdstringNoExternal subscription id from your payment provider.
subscriptionInterval'monthly' | 'quarterly' | 'annual'NoBilling cadence for subscription sales.
eventNamestringNoCustom 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.

FieldTypeRequiredDescription
affiliateUrlstringNoThe full affiliate URL containing the ?aff= parameter.
pageUrlstringNoThe landing page URL.
referrerUrlstringNoThe HTTP referrer.
existingClickIdstringNoPass 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 pass 0) for a full refund → commission status set to reversed.
  • Pass a partial amount → commission status set to refunded (proportional).

invoiceId is required and throws synchronously if missing.

FieldTypeRequiredDescription
invoiceIdstringYesThe invoiceId you passed to trackSale — identifies which sale to refund.
refundAmountCentsnumberNoRefund amount in integer cents. Omit or pass 0 for a full refund.
refundReasonstringNoOptional 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,
    });
  }
}

Edit on GitHub
© 2026 Affitor