3-Step Integration Guide
Get Affitor tracking live with the supported click, signup, and server-side tracking flows
Three steps get Affitor tracking live: clicks → signups → sales. Steps 1 and 2 establish attribution; step 3 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 (server-side tracking vs Stripe integration):
- Server-side tracking — your backend calls
POST /api/v1/track/sale— for any backend or payment provider - Stripe integration — attach Affitor metadata to Stripe Checkout sessions
- Server-side tracking — your backend calls
Use one naming scheme consistently:
customerKey— browser helper argument (signup(customerKey, email))customer_key— tracking API fieldaffitor_customer_key— Stripe metadata field
Step 1 — Install the Tracker
Add the tracker to every page where affiliate traffic can land.
Once the tracker is live on your public site, Affitor detects it automatically — no debug mode needed. To verify the full pipeline, use the Run verification button on the Share Program step (it runs a synthetic click → signup → sale). See Testing Integration.
<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_idin 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 signup succeeds, tell Affitor which internal user was created.
Option A — Browser-side helper
Use this when your signup flow runs in the browser and the tracker is already 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 signup logic lives on your backend or you want the 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_idcustomer_key
- For reliable payment attribution later, always send a stable internal customer ID:
customerKeyinsignup()customer_keyin the API
- Browser-side
signup()is the easiest path for most teams - Server-side API is best when signup completes on your backend
Response:
{
"success": true,
"message": "Lead tracked successfully"
}Step 3 — Track Sales
When a customer pays, choose one revenue-tracking path.
Option A — Server-side tracking (works with any payment provider)
Send a server-to-server request once 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 }
]
}| Parameter | Required | Type | Description |
|---|---|---|---|
transaction_id | Yes | string | Unique transaction identifier used for deduplication |
amount_cents | Yes | number | Positive integer amount in cents |
customer_key | Conditional | string | Your internal customer ID |
click_id | Conditional | string | Tracked click identifier |
currency | No | string | USD, EUR, or VND. Default: USD |
sale_type | No | string | payment or subscription. Default: payment |
is_recurring | No | boolean | Use true for recurring charges |
subscription_id | No | string | External subscription ID |
subscription_interval | No | string | e.g. monthly, quarterly, annual |
product_id | No | string | External product identifier |
line_items | No | array | Optional 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"
}transaction_id must be unique. Reusing the same value returns 409 Conflict.
Option B — Stripe integration
If you already use Stripe Checkout, keep taking payment in your own Stripe account — just 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:
metadatasubscription_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 integration rules
- One-time payments are attributed from Stripe checkout metadata and webhook processing
- Subscriptions and renewals rely on
invoice.payment_succeeded - If
subscription_data.metadatais missing, renewal attribution can fail - Required metadata fields:
affitor_click_idaffitor_customer_keyprogram_id
Which Sales Path Should You Use?
| Path | Best for |
|---|---|
Server-side tracking — POST /api/v1/track/sale | Any backend, any payment provider, custom checkout, Paddle/LemonSqueezy/manual server events |
| Stripe integration | Existing Stripe Checkout integration |
Use server-side tracking when your backend controls the revenue event. Use Stripe integration when you want Affitor to attribute sales directly from your Stripe webhook flow.
Verify it worked
- Visit your site with `?aff=TESTCODE` — an `affitor_click_id` cookie is set
- Your signup handler's `trackLead` call returns 2xx; your Stripe webhook (or S2S call) fires `trackSale` on a test payment
- Click, lead, and sale all appear under your program's tracking events — or run `npx affitor onboard verify` to fire the whole synthetic chain in one command
- Use `additional_data: { test_mode: true }` so test events never create real commissions
You're Done
All three steps working means your integration is live:
| Step | Event | Outcome |
|---|---|---|
| Tracker installed | Click/pageview tracking | affiliate visit captured |
| Signup tracked | Lead event | customer linked to partner |
| Sale tracked | Sale + commission flow | revenue attributed |
Next: