Affitor

3-Step Integration Guide

Get Affitor tracking live with the supported click, signup, and sale flows

Get Affitor tracking live in three steps: clicks → signups → sales. The first two steps establish attribution; the third step records revenue.

Before You Start

Make sure you have:

  • Your program ID
  • Your program API key for server-to-server calls
  • A decision on how you will track sales:
    • Sale API for any backend or payment provider
    • Stripe metadata + webhook for Stripe Checkout / Bill Flow

:::tip[Get all of this in one command] Run npx affitor init to create your program and receive your program ID, API key, and ready-to-use tracking snippets. See the CLI Quickstart. :::

:::note Use one naming scheme consistently:

  • customerKey — browser helper argument (signup(customerKey, email))
  • customer_key — lead/sale API field
  • affitor_customer_key — Stripe metadata field :::

Step 1 — Install the Tracker

Add the Affitor tracker to every page where affiliate traffic can land.

<script
  src="https://api.affitor.com/js/affitor-tracker.js"
  data-affitor-program-id="YOUR_PROGRAM_ID"
  data-affitor-debug="false">
</script>

Replace YOUR_PROGRAM_ID with the program ID from your dashboard.

What this does

  • Detects affiliate visits via the ?aff= parameter
  • Creates a tracked customer / click relationship
  • Stores affitor_click_id in a first-party cookie
  • Sends click/pageview tracking data to Affitor

Verify

Visit your site through a real affiliate link or enable debug mode while testing. Then review:

  • browser console / network requests
  • Affitor dashboard → tracking/integration pages

Step 2 — Track Signups

After a signup succeeds, tell Affitor which internal user/customer was created.

Option A — Browser-side helper

Use this when your signup flow already runs in the browser and the tracker is loaded.

<script>
await window.affitor.signup('user_123', 'user@example.com');
</script>

Or inside your signup flow:

<script>
document.getElementById('signup-form').addEventListener('submit', async function(e) {
  e.preventDefault();

  const email = document.getElementById('email').value;

  // Run your own signup logic first.
  // After account creation succeeds:
  await window.affitor.signup(currentUser.id, email);
});
</script>

Use this when:

  • you already installed the tracker on the page
  • signup happens in the browser
  • you want the simplest supported flow

Option B — Server-side lead API

Use this when your signup logic lives on your backend or you want your server to own the tracking call.

POST https://api.affitor.com/api/v1/track/lead
Authorization: Bearer YOUR_PROGRAM_API_KEY
Content-Type: application/json

{
  "click_id": "cust_42_1234567890",
  "customer_key": "user_123",
  "email": "user@example.com"
}
await fetch('https://api.affitor.com/api/v1/track/lead', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.AFFITOR_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    click_id: req.cookies.affitor_click_id,
    customer_key: newUser.id,
    email: newUser.email,
  }),
});

Lead tracking rules

  • Outside test mode, provide at least one of:
    • click_id
    • customer_key
  • For reliable payment attribution later, always send a stable internal customer ID:
    • customerKey in signup()
    • customer_key in the API
  • Browser-side signup() is the easiest supported path for most teams
  • Server-side API is best when signup completion happens on your backend

Response:

{
  "success": true,
  "message": "Lead tracked successfully"
}

Step 3 — Track Sales

Once a customer pays, choose one supported revenue-tracking path.

Option A — Sale API (works with any payment provider)

Send a server-to-server request after payment succeeds.

Endpoint: POST https://api.affitor.com/api/v1/track/sale

{
  "transaction_id": "txn_abc123",
  "customer_key": "user_123",
  "click_id": "cust_42_123456",
  "amount_cents": 9999,
  "currency": "USD",
  "sale_type": "payment",
  "is_recurring": false,
  "subscription_id": "sub_xyz",
  "subscription_interval": "monthly",
  "product_id": "prod_456",
  "line_items": [
    { "name": "Pro Plan", "amount_cents": 9999, "quantity": 1 }
  ]
}
ParameterRequiredTypeDescription
transaction_idYesstringUnique transaction identifier used for deduplication
amount_centsYesnumberPositive integer amount in cents
customer_keyConditionalstringYour internal customer ID
click_idConditionalstringTracked click identifier
currencyNostringUSD, EUR, or VND. Default: USD
sale_typeNostringpayment or subscription. Default: payment
is_recurringNobooleanUse true for recurring charges
subscription_idNostringExternal subscription ID
subscription_intervalNostringe.g. monthly, quarterly, annual
product_idNostringExternal product identifier
line_itemsNoarrayOptional item breakdown
await fetch('https://api.affitor.com/api/v1/track/sale', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.AFFITOR_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    transaction_id: order.id,
    customer_key: currentUser.id,
    click_id: req.cookies.affitor_click_id,
    amount_cents: order.totalCents,
    currency: 'USD',
    sale_type: 'payment',
  }),
});

Response:

{
  "success": true,
  "sale_id": 42,
  "commission_id": 18,
  "message": "Sale tracked successfully"
}

:::note transaction_id must be unique. Reusing the same value returns 409 Conflict. :::

Option B — Stripe Checkout metadata (Bill Flow)

If you already use Stripe Checkout, keep taking payment in your own Stripe account and attach Affitor metadata to the checkout session.

const session = await stripe.checkout.sessions.create({
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  mode: 'payment',
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
  metadata: {
    affitor_click_id: clickId,
    affitor_customer_key: currentUser.id,
    program_id: 'YOUR_PROGRAM_ID',
  },
});

For subscriptions

Include the same fields in both places:

  • metadata
  • subscription_data.metadata
const session = await stripe.checkout.sessions.create({
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  mode: 'subscription',
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
  metadata: {
    affitor_click_id: clickId,
    affitor_customer_key: currentUser.id,
    program_id: 'YOUR_PROGRAM_ID',
  },
  subscription_data: {
    metadata: {
      affitor_click_id: clickId,
      affitor_customer_key: currentUser.id,
      program_id: 'YOUR_PROGRAM_ID',
    },
  },
});

Stripe/Bill Flow rules

  • One-time payments are attributed from Stripe checkout metadata and webhook processing
  • Subscriptions and renewals rely on invoice.payment_succeeded
  • If subscription_data.metadata is missing, renewal attribution can fail
  • Public recommended fields are:
    • affitor_click_id
    • affitor_customer_key
    • program_id

Which Sales Path Should You Use?

PathBest for
Sale APIAny backend, any payment provider, custom checkout, Paddle/LemonSqueezy/manual server events
Stripe metadata + webhookExisting Stripe Checkout integration using Bill Flow

Use the Sale API when your backend already knows exactly when revenue is finalized. Use Stripe metadata when you want Affitor to attribute sales directly from your Stripe webhook flow.


You're Done

Once all three steps are working, your integration is live:

StepEventOutcome
Tracker installedClick/pageview trackingaffiliate visit captured
Signup trackedLead eventcustomer linked to partner
Sale trackedSale + commission flowrevenue attributed

Next:

Edit on GitHub