Lead Tracking (Signup)

Track referred signups with the browser helper or the server-side lead API

Lead tracking connects a signup to the affiliate click that drove it — turning anonymous referral traffic into an identified customer record.


What Lead Tracking Does

When a lead settles, Affitor:

  1. links the signup to the tracked click/customer relationship
  2. stores your internal customer identifier for later payment attribution
  3. advances the customer journey from click → lead

A lead sent from your server with your program API key settles right away. On new programs, a lead sent only from the browser is stored as provisional and does none of the above until it is confirmed (see Lead auth policy).

Use the same customer identifier across all three contexts:

ContextField
Browser helpercustomerKey
Lead APIcustomer_key
Stripe metadata lateraffitor_customer_key

Prerequisites

Before tracking a signup:

  • the visitor arrived through an affiliate-tracked flow
  • the tracker created a click/customer relationship first
  • your signup flow has a stable internal user/customer ID to pass to Affitor

A tracked click means the browser already has affitor_click_id from the pageview/click step.


Send the lead from your backend with your program API key. This is the path that settles the lead: new programs require server-verified leads (see Lead auth policy).

Endpoint

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

Request body

{
  "click_id": "cust_42_1234567890",
  "customer_key": "usr_abc123",
  "email": "user@example.com"
}

Rules

Outside test mode, provide at least one of click_id or customer_key. Send both whenever possible for reliable downstream payment attribution.

Response

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

Node.js example

app.post('/signup', async (req, res) => {
  const { email, password } = req.body;
  const newUser = await createUser({ email, password });
  const clickId = req.cookies.affitor_click_id;

  await fetch('https://api.affitor.com/api/v1/track/lead', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.AFFITOR_API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      click_id: clickId,
      customer_key: newUser.id,
      email: newUser.email,
    }),
  });

  res.json({ success: true });
});

Python example

requests.post(
    'https://api.affitor.com/api/v1/track/lead',
    headers={
        'Authorization': f'Bearer {AFFITOR_API_TOKEN}',
        'Content-Type': 'application/json',
    },
    json={
        'click_id': request.cookies.get('affitor_click_id'),
        'customer_key': str(new_user.id),
        'email': new_user.email,
    },
)

Option B — Browser-side signup()

Use this when signup completes in the browser and the tracker script is loaded on the page.

On programs that require server-verified leads (the default for new programs), a browser signup() call is recorded as provisional. It does not settle the lead, does not save your customerKey on the customer, and does not count in partner metrics or pay a per-lead commission. It settles only when your backend sends the same lead (Option A) or a Stripe Checkout payment for that customer arrives through your connected Stripe account. A sale sent to /api/v1/track/sale does not settle it. Use signup() alongside the server-side call, not instead of it.

Basic example

if (window.affitor) {
  await window.affitor.signup('user_123', 'user@example.com');
} else {
  window.affitorQueue = window.affitorQueue || [];
  window.affitorQueue.push(['signup', 'user_123', 'user@example.com']);
}

What the arguments mean

ArgumentRequiredMeaning
customerKeyYes (recommended for all real integrations)your internal user/customer ID
emailRecommendedcustomer email; used for hashed/masked attribution support
Use a real internal identifier

The customerKey you pass here should be the same identifier you later use as:

  • customer_key in the tracking API
  • affitor_customer_key in Stripe metadata

Example inside a signup flow

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

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

  // Your own signup logic first
  const result = await createAccount(email);

  if (result.success) {
    if (window.affitor) {
      await window.affitor.signup(result.userId, email);
    } else {
      window.affitorQueue = window.affitorQueue || [];
      window.affitorQueue.push(['signup', result.userId, email]);
    }
  }
});

When to use browser-side signup

  • your frontend knows when signup succeeds
  • the tracker is installed on the page
  • you also send the lead from your backend (Option A), or your program allows browser leads

Browser Mode vs Server Mode

ModeBest forAuth
POST /api/v1/track/leadbackend-driven signup flows (recommended)Bearer program API key required for the lead to settle
signup(customerKey, email)frontend/browser signup flowsno Bearer key; provisional on programs that require server-verified leads

A call without a Bearer key is treated as browser mode, even when it comes from your server. Both modes must identify the same customer with the same internal ID.

Lead auth policy

Each program has a lead auth policy. New programs default to server-verified leads (server_required):

  • a browser-mode lead returns 200 with "provisional": true and is stored, but it does not settle attribution, save customer_key, count in partner metrics, or pay a per-lead commission
  • the lead settles when your backend sends it with a Bearer key, or when a Stripe Checkout payment for that customer arrives through your connected Stripe account (a sale sent to /api/v1/track/sale does not settle it)

Programs created before this policy existed keep the older behavior, where a browser lead settles directly. The Lead capture mode line in your program's tracking settings shows which policy applies.


Test Mode

Create a test lead event to verify your integration without affecting production attribution.

curl -X POST https://api.affitor.com/api/v1/track/lead \
  -H "Authorization: Bearer YOUR_PROGRAM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "click_id": "test_lead_001",
    "customer_key": "test_customer",
    "additional_data": {
      "test_mode": true,
      "program_id": "YOUR_PROGRAM_ID"
    }
  }'

Test mode behavior

  • creates a test lead event marked is_test: true
  • does not create a real production lead/customer transition
  • counts toward integration verification only when sent with your program API key; without the Bearer header the event is stored but does not advance verification

Test mode response

{
  "success": true,
  "message": "Test lead event tracked successfully",
  "data": {
    "eventId": 456,
    "programId": 1,
    "test_mode": true
  }
}

Common Failure Modes

Lead not attributed

Check that:

  • the visitor came through a tracked affiliate click
  • customerKey / customer_key matches your real internal user ID
  • affitor_click_id was available when expected
  • you are not testing only in debug mode

Wrong identifier used later in payments

Mismatched identifiers are the most common cause of broken attribution. The same internal ID must be used across:

  • signup(customerKey, email)
  • customer_key in server-side tracking calls
  • affitor_customer_key in Stripe metadata

Duplicate signup tracking

To prevent duplicates:

  • call signup tracking only after account creation succeeds
  • guard against duplicate frontend submissions
  • deduplicate your signup flow before retrying tracking calls

What to Do Next

Once lead tracking is working, set up sale tracking:

© 2026 Affitor