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 { loadAffitor } from '@affitor/sdk';

// On app load (client-side; resolves to null on the server)
const affitor = await loadAffitor(123);

// After the user completes signup
await affitor?.signup('user_abc123', 'user@example.com');

loadAffitor loads the tracker script once and resolves to the same instance on every later call. The tracker captures ?aff= from the URL and sets the click id cookie by itself.

The tracker sends events to https://api.affitor.com. To send them to a non-production API, set window.AFFITOR_API_BASE = '<API_BASE>' before the first loadAffitor call, or use the script tag with data-affitor-api-base.


loadAffitor(programId, options?)

OptionTypeDefaultDescription
env'production' | 'local''production'Selects the tracker script URL.
scriptUrlstringnoneCustom tracker script URL; overrides env. It only chooses where the script loads from.
debugbooleanfalseVerbose console.log output. Errors always log.

Instance methods

MethodSignatureDescription
signup(customerKey: string, email: string) => Promise<void>Report a lead (signup). Pass your internal user id as customerKey.
getClickId() => string | nullThe current click id from memory or the affitor_click_id cookie.
getVisitorId() => string | nullThe visitor id from Affitor's first-party cookie, or null. Put it in checkout metadata as affitor_visitor_id. With consent mode on, call it after consent(true).
consent(granted: boolean) => voidWith data-affitor-consent="required" on the script, nothing is stored or sent until consent(true); consent(false) removes Affitor's cookies.
getData() => AffitorDataA snapshot of tracker state.
trackTest(data?) => voidSend a test event.

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

AffitorData

Returned by getData().

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

Full example (Next.js)

// app/affitor-tracker.tsx — client component
'use client';
import { useEffect } from 'react';
import { loadAffitor } from '@affitor/sdk';

export function AffitorTracker() {
  useEffect(() => {
    void loadAffitor(YOUR_PROGRAM_ID);
  }, []);
  return null;
}
// render <AffitorTracker /> once in app/layout.tsx

After a successful signup, call signup(customerKey, email) on the instance (or window.affitor.signup(...) for the script-tag path):

import { loadAffitor } from '@affitor/sdk';

const affitor = await loadAffitor(YOUR_PROGRAM_ID);
await affitor?.signup(userId, email);

React apps can also use AffitorProvider and useAffitor() from @affitor/sdk/react.


@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.
currencystringNoUSD, EUR, or VND; other codes are not supported. Defaults to USD. Amounts are not converted between currencies: commissions and platform fees are calculated on the amount as sent, and invoices are issued in 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 it once per refund, from your payment provider's refund webhook.

  • Omit refundAmountCents (or pass 0) to refund everything not yet refunded → commission status set to reversed.
  • Pass this refund's own amount → it is added to what was already refunded on the sale, and the commission is reduced proportionally (refunded, or reversed once the total reaches the sale amount).
  • Do not pass a running total (such as Stripe's charge.amount_refunded): each call is counted as a new refund.
  • A retry with the same amount is ignored. To report two separate refunds of the same amount, call the HTTP endpoint with a distinct refund_id for each.

invoiceId is required and throws synchronously if missing.

FieldTypeRequiredDescription
invoiceIdstringYesThe invoiceId you passed to trackSale — identifies which sale to refund.
refundAmountCentsnumberNoThis refund's amount in integer cents (not the running total). Omit or pass 0 to refund the rest of the sale.
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);
    }
  }

  // One event per refund, carrying that refund's own amount.
  if (event.type === 'refund.created') {
    const refund = event.data.object as Stripe.Refund;
    await affitor.trackRefund({
      invoiceId: refund.payment_intent as string,
      refundAmountCents: refund.amount,
    });
  }
}

© 2026 Affitor