Skip to content

Lead Tracking (Signup)

Lead tracking captures when referred visitors sign up or submit forms. This lets you see which partners drive qualified leads—not just clicks.


When a visitor signs up on your site, you call trackLead() to record the lead. This:

  1. Validates that user_id is provided (critical for payment tracking)
  2. Links the lead event to the stored customer_code from the click
  3. Sends lead data to Affitor API for processing
  4. Updates the customer-partner relationship status to “lead”

Partners can then see: Clicks → Signups → Sales

Note: In debug mode, calling trackLead() sends a test event instead of a real lead event. This helps verify your integration without creating actual lead records.


Before implementing lead tracking:

  • Pageview tracker installed (guide)
  • Visitor arrived via affiliate link and has been tracked (creates customer_code in database)
  • customer_code cookie exists in browser (set automatically by pageview tracker)

Important: The lead event requires the customer_code cookie to link the signup to the affiliate partner. If this cookie is missing or the customer wasn’t tracked via an affiliate link, the lead tracking will fail silently (no error, but lead won’t be attributed).


After your signup form submits successfully, call trackLead().

import { loadAffitor } from '@affitor/tracker';
// Awaits script load — guaranteed to fire
const affitor = await loadAffitor('YOUR_PROGRAM_ID');
affitor?.trackLead({
email: 'user@example.com',
user_id: 'user_123' // Your internal user ID - REQUIRED
});
var leadData = {
email: 'user@example.com',
user_id: 'user_123' // Your internal user ID - REQUIRED
};
if (window.affitor) {
window.affitor.trackLead(leadData);
} else {
window.affitorQueue = window.affitorQueue || [];
window.affitorQueue.push(['trackLead', leadData]);
}

Include additional data for better analytics:

import { loadAffitor } from '@affitor/tracker';
const affitor = await loadAffitor('YOUR_PROGRAM_ID');
affitor?.trackLead({
email: 'user@example.com', // Required
user_id: 'user_123', // Required - YOUR internal user ID
additional_data: {
signup_method: 'email', // Optional - How they signed up
plan: 'free_trial', // Optional - Plan they selected
source: 'pricing_page' // Optional - Where they signed up
}
});
var leadData = {
email: 'user@example.com', // Required
user_id: 'user_123', // Required - YOUR internal user ID
name: 'John Doe', // Optional
phone: '+1234567890', // Optional
additional_data: {
signup_method: 'email', // Optional - How they signed up
plan: 'free_trial', // Optional - Plan they selected
source: 'pricing_page' // Optional - Where they signed up
}
};
if (window.affitor) {
window.affitor.trackLead(leadData);
} else {
window.affitorQueue = window.affitorQueue || [];
window.affitorQueue.push(['trackLead', leadData]);
}
ParameterTypeRequiredDescription
emailstringRecommendedUser’s email address (hashed for privacy before storage)
user_idstringRequiredYour internal user ID - critical for payment attribution
namestringNoUser’s full name (NOT stored - discarded per privacy policy)
phonestringNoUser’s phone number (NOT stored - discarded per privacy policy)
additional_dataobjectNoAny extra data you want to track (stored as-is)

Privacy Notes:

  • email is hashed (SHA-256) and masked before storage - the original email is never stored
  • name and phone are intentionally discarded by the backend and never stored
  • Only email_hash, email_masked, and user_id are persisted in the database

import { loadAffitor } from '@affitor/tracker';
function SignupForm() {
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
// Your signup logic
const result = await api.createAccount({
email: formData.get('email'),
name: formData.get('name')
});
if (result.success) {
// ✅ Awaits script load — guaranteed to fire
const affitor = await loadAffitor('YOUR_PROGRAM_ID');
affitor?.trackLead({
email: formData.get('email') as string,
user_id: result.userId, // ✅ REQUIRED: Your internal user ID
additional_data: {
signup_method: 'react_form'
}
});
router.push('/welcome');
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="name" type="text" />
<button type="submit">Sign Up</button>
</form>
);
}
import { loadAffitor } from '@affitor/tracker';
async function handleOAuthCallback(user) {
// User authenticated via Google/GitHub/etc.
// ✅ Awaits script load — guaranteed to fire
const affitor = await loadAffitor('YOUR_PROGRAM_ID');
affitor?.trackLead({
email: user.email,
user_id: user.id, // ✅ REQUIRED: Your internal user ID
additional_data: {
signup_method: 'google'
}
});
window.location.href = '/dashboard';
}
document.getElementById('signup-form').addEventListener('submit', async function(e) {
e.preventDefault();
var email = document.getElementById('email').value;
var name = document.getElementById('name').value;
// Your signup logic
var result = await createAccount(email, name);
if (result.success) {
// Track the lead with user_id (REQUIRED)
var leadData = {
email: email,
user_id: result.userId, // ✅ REQUIRED: Your internal user ID
name: name,
additional_data: { signup_method: 'form' }
};
if (window.affitor) {
window.affitor.trackLead(leadData);
} else {
window.affitorQueue = window.affitorQueue || [];
window.affitorQueue.push(['trackLead', leadData]);
}
window.location.href = '/welcome';
}
});

Call trackLead() after the signup is confirmed successful:

ScenarioWhen to track
Form signupAfter form validation passes and account is created
OAuth signupAfter OAuth callback, user authenticated
Email verificationAfter signup, not after verification (track intent)
Free trialWhen trial starts
WaitlistWhen added to waitlist

Don’t track:

  • Failed signups
  • Duplicate signups
  • Bot submissions

Section titled “npm SDK — await loadAffitor() (Recommended)”

The npm SDK solves all timing issues with a Promise-based API. loadAffitor() returns a singleton Promise that resolves once the tracker script is loaded. No race conditions, no queue management, full TypeScript support.

import { loadAffitor } from '@affitor/tracker';
// Always works — awaits script load automatically
const affitor = await loadAffitor('YOUR_PROGRAM_ID');
affitor?.trackLead({ email, user_id: userId });

If you’re using the script tag approach, always check if window.affitor exists first. If it does, call the method directly. If not, use the queue as a fallback for the case where the script hasn’t loaded yet.

var leadData = { email: email, user_id: userId };
if (window.affitor) {
window.affitor.trackLead(leadData);
} else {
window.affitorQueue = window.affitorQueue || [];
window.affitorQueue.push(['trackLead', leadData]);
}

If you process signups on your backend server, you can track leads directly via the REST API instead of using client-side JavaScript. This is ideal for server-rendered apps or when you want to avoid any client-side tracking code on your signup page.

Client-side (trackLead())Server-side API
Simple integrationNo JS required on signup page
Requires affitor-tracker.jsWorks in any server language
Cookie read by browser JSCookie read by your server
Best for SPAs / client-rendered appsBest for server-rendered apps

Both approaches use customer_code for attribution. The client-side trackLead() reads the cookie automatically; the server-side API requires your server to read the cookie value and pass it explicitly.

Authorization: Bearer affitor_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Find your API token in the dashboard under Settings → Referrals Tracker → Server-Side API.

1. User visits your landing page with ?ref=partner123
2. Affitor client-side tracker fires → sets affitor_customer cookie
3. User fills out your signup form and submits
4. Your frontend reads cookie for affitor_customer → sends value to your server
5. Your server calls the Affitor API with customer_code
6. Lead is attributed to the correct affiliate partner
POST https://api.affitor.com/api/tracking/lead
Authorization: Bearer affitor_xxxxxx
Content-Type: application/json
{
"customer_code": "cust_42_1234567890", // REQUIRED — from affitor_customer cookie
"user_id": "usr_abc123", // REQUIRED
"email": "user@example.com" // optional
}
{
"success": true,
"message": "Lead tracked successfully",
"customer_code": "cust_42_1234567890"
}
app.post('/signup', async (req, res) => {
const { email, password } = req.body;
// 1. Create user in your database
const newUser = await createUser({ email, password });
// 2. Read the affitor_customer cookie (set by client-side tracker)
const customerCode = req.cookies.affitor_customer;
// 3. Track lead via Affitor API (only if customer came via affiliate link)
if (customerCode) {
await fetch('https://api.affitor.com/api/tracking/lead', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.AFFITOR_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_code: customerCode,
user_id: newUser.id,
email: newUser.email,
}),
});
}
res.json({ success: true });
});
@app.route('/signup', methods=['POST'])
def signup():
data = request.get_json()
# 1. Create user in your database
new_user = create_user(data['email'], data['password'])
# 2. Read the affitor_customer cookie
customer_code = request.cookies.get('affitor_customer')
# 3. Track lead via Affitor API
if customer_code:
requests.post(
'https://api.affitor.com/api/tracking/lead',
headers={
'Authorization': f'Bearer {AFFITOR_API_TOKEN}',
'Content-Type': 'application/json',
},
json={
'customer_code': customer_code,
'user_id': str(new_user.id),
'email': new_user.email,
},
)
return jsonify({'success': True})
public function signup(Request $request) {
// 1. Create user in your database
$user = User::create([
'email' => $request->email,
'password' => bcrypt($request->password),
]);
// 2. Read the affitor_customer cookie
$customerCode = $request->cookie('affitor_customer');
// 3. Track lead via Affitor API
if ($customerCode) {
Http::withHeaders([
'Authorization' => 'Bearer ' . env('AFFITOR_API_TOKEN'),
])->post('https://api.affitor.com/api/tracking/lead', [
'customer_code' => $customerCode,
'user_id' => (string) $user->id,
'email' => $user->email,
]);
}
return response()->json(['success' => true]);
}
def signup
# 1. Create user in your database
user = User.create!(email: params[:email], password: params[:password])
# 2. Read the affitor_customer cookie
customer_code = cookies[:affitor_customer]
# 3. Track lead via Affitor API
if customer_code.present?
uri = URI('https://api.affitor.com/api/tracking/lead')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Post.new(uri, {
'Authorization' => "Bearer #{ENV['AFFITOR_API_TOKEN']}",
'Content-Type' => 'application/json',
})
req.body = {
customer_code: customer_code,
user_id: user.id.to_s,
email: user.email,
}.to_json
http.request(req)
end
render json: { success: true }
end

To verify your API token and integration work correctly, add "test_mode": true to the additional_data field:

{
"additional_data": {
"test_mode": true
}
}

In test mode:

  • customer_code and user_id are not required
  • No real lead record is created
  • A test event is recorded that the dashboard can verify

After sending a test request, go to Settings → Referrals Tracker and click Test Lead Tracking to confirm the event was received. Remove test_mode when you go to production.

StatusMeaning
401Missing or invalid API token
403Program is not active
400Missing customer_code or user_id
404Customer not found or not in this program
500Internal server error

Enable debug mode in the pageview tracker:

npm SDK:

const affitor = await loadAffitor('YOUR_PROGRAM_ID', { debug: true });

Script tag:

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

What happens when you call trackLead() in debug mode:

  1. The tracker sends a test event instead of a real lead event
  2. No actual lead record is created in the database
  3. Console shows validation messages for user_id
  4. You’ll see these console messages:

With user_id provided:

[Affitor Tracker] ✅ Affitor: user_id detected - user_123
[Affitor Tracker] trackTest() called true
[Affitor Tracker] Sending test event to verify tracking: {customerCode: ..., hasAttribution: true, eventType: "test"}

Without user_id:

[Affitor Tracker] ❌ Affitor: user_id is REQUIRED for trackLead() - payment attribution will not work without it!
[Affitor Tracker] Please include user_id in your trackLead call:
[Affitor Tracker] window.affitor.trackLead({ email: "user@example.com", user_id: "user_123", ... })
[Affitor Tracker] trackTest() called true

Important: In debug mode, trackLead() always sends a test event and returns immediately without calling the real API. This prevents accidental test leads in your database.

  1. Visit your site
  2. Go to Affitor dashboard → Setting -> Referrals Tracker
  3. Click on Test Lead Tracking
  4. Referrals Tracker should show “Connected” with a blue checkmark

Check:

  • window.affitor exists (pageview tracker loaded)
  • customer_code cookie exists (visitor came via affiliate link)
  • No JavaScript errors in console
  • Not in debug mode (debug mode sends test events only)

Check:

  • Visitor came via affiliate link (?ref=PARTNER123) before signing up
  • customer_code cookie exists and matches a tracked click in the database
  • Attribution window hasn’t expired (60 days default)
  • Affiliate customer record exists in database (created during click tracking)

Common issue: If the visitor’s first page load didn’t include ?ref=, no customer record was created, so the lead cannot be attributed even if cookies exist.

This is expected behavior when user_id is missing:

❌ Affitor: user_id is REQUIRED for trackLead() - payment attribution will not work without it!

Solutions:

  • Always include user_id in your trackLead() call
  • user_id should be your internal user identifier (from your database)
  • The same user_id must be used in Stripe checkout metadata

Note: The lead is still tracked even without user_id, but payment attribution will fail later.

Prevent by:

  • Only calling trackLead() once per signup
  • Checking signup success before tracking
  • Backend automatically handles duplicate lead tracking (updates existing customer instead of creating new lead)

Check:

  • Lead event was successfully sent (check browser Network tab)
  • Customer-partner relationship exists in database
  • Lead status updated to “lead” (was previously “click”)
  • Dashboard filters aren’t hiding the lead

Lead tracking is set up. Now capture sales:

Set Up Payment Tracking →