URL-based tracking enables attribution, analytics, and user journey analysis. From simple UTM parameters to sophisticated click IDs, tracking URLs help understand user behavior.
Key Takeaways
- 1Click IDs enable individual-level attribution
- 2Combine URL tracking with cookies for full journeys
- 3Preserve tracking params through redirects
- 4Balance tracking depth with URL readability
- 5Comply with privacy regulations (GDPR, CCPA)
“Campaign parameters allow you to identify the campaigns that send traffic to your site, enabling accurate attribution of conversions to specific marketing efforts.”
Tracking Parameter Types
Different tracking parameters serve different purposes. Some identify campaigns, others track individual clicks, and some maintain session context. The table below summarizes common types.
| Type | Purpose | Example |
|---|---|---|
| UTM | Campaign attribution | utm_source=google |
| Click ID | Individual click | gclid=abc123 |
| Session ID | User session | sid=xyz789 |
| Event ID | Specific action | event=button-click |
| Referrer ID | Source tracking | ref=partner123 |
UTM parameters are the most common for campaign tracking. Click IDs (like gclid from Google Ads) enable cross-platform attribution and individual-level tracking.
Click Tracking Implementation
Server-side click tracking gives you full control over what data you collect and how clicks are attributed. A tracking redirect endpoint logs the click before forwarding users to their destination.
// Server-side click tracking
// Tracking redirect endpoint
// GET /track?url=https://example.com&event=cta-click&source=email
app.get('/track', async (req, res) => {
const { url, event, source } = req.query;
// Generate unique click ID
const clickId = crypto.randomBytes(16).toString('hex');
// Log the click
await db.clicks.create({
clickId,
destinationUrl: url,
event,
source,
timestamp: new Date(),
ip: req.ip,
userAgent: req.headers['user-agent'],
referrer: req.headers['referer']
});
// Add click ID to destination URL
const destination = new URL(url);
destination.searchParams.set('_clid', clickId);
// Redirect to destination
res.redirect(302, destination.toString());
});
// Track conversion with click ID
app.post('/api/convert', async (req, res) => {
const clickId = req.query._clid || req.cookies._clid;
if (clickId) {
const click = await db.clicks.findOne({ clickId });
if (click) {
await db.conversions.create({
clickId,
orderId: req.body.orderId,
value: req.body.value
});
}
}
});The tracking endpoint generates a unique click ID, logs the click with user context, and appends the click ID to the destination URL. Later, conversion tracking can match purchases to specific clicks.
Client-side tracking complements server-side approaches by capturing interactions that happen entirely in the browser. It is useful for tracking internal navigation and user engagement.
Client-Side Tracking
For tracking clicks within your own site, client-side JavaScript can send events to your analytics endpoint without requiring page reloads. This is especially useful for single-page applications.
// Client-side click tracking
// Track link clicks
document.addEventListener('click', (e) => {
const link = e.target.closest('a[data-track]');
if (!link) return;
const trackingData = {
event: link.dataset.track,
href: link.href,
text: link.textContent,
timestamp: Date.now(),
page: window.location.pathname
};
// Send to analytics
navigator.sendBeacon('/api/track', JSON.stringify(trackingData));
});
// Add tracking params to outbound links
function decorateLink(url, params) {
const decorated = new URL(url);
Object.entries(params).forEach(([key, value]) => {
decorated.searchParams.set(key, value);
});
return decorated.toString();
}
// Usage
const trackedLink = decorateLink('https://partner.com/signup', {
ref: 'mysite',
campaign: 'homepage-cta',
click_id: generateClickId()
});
// Preserve tracking params on internal navigation
function preserveTrackingParams(newUrl) {
const current = new URLSearchParams(window.location.search);
const next = new URL(newUrl, window.location.origin);
// Preserve these tracking params
['utm_source', 'utm_medium', 'utm_campaign', 'gclid', 'fbclid'].forEach(param => {
if (current.has(param) && !next.searchParams.has(param)) {
next.searchParams.set(param, current.get(param));
}
});
return next.toString();
}Using navigator.sendBeacon ensures the tracking request completes even if the user navigates away quickly. The preserveTrackingParams function maintains attribution across internal page transitions.
Understanding the full customer journey requires tracking multiple touchpoints over time. Multi-touch attribution records each interaction leading to a conversion.
Attribution Tracking
Users often interact with your brand multiple times before converting. Multi-touch attribution captures each touchpoint so you can understand which channels contribute to conversions.
// Multi-touch attribution tracking
class AttributionTracker {
constructor() {
this.storageKey = 'attribution_touches';
}
recordTouch() {
const params = new URLSearchParams(window.location.search);
const touch = {
timestamp: Date.now(),
source: params.get('utm_source'),
medium: params.get('utm_medium'),
campaign: params.get('utm_campaign'),
referrer: document.referrer,
landingPage: window.location.pathname,
clickId: params.get('gclid') || params.get('fbclid') || params.get('_clid')
};
// Only record if has tracking data
if (touch.source || touch.clickId || touch.referrer) {
const touches = this.getTouches();
touches.push(touch);
// Keep last 10 touches
const trimmed = touches.slice(-10);
localStorage.setItem(this.storageKey, JSON.stringify(trimmed));
}
}
getTouches() {
try {
return JSON.parse(localStorage.getItem(this.storageKey)) || [];
} catch {
return [];
}
}
getFirstTouch() {
return this.getTouches()[0];
}
getLastTouch() {
const touches = this.getTouches();
return touches[touches.length - 1];
}
getConversionData() {
const touches = this.getTouches();
return {
firstTouch: touches[0],
lastTouch: touches[touches.length - 1],
touchCount: touches.length,
allTouches: touches
};
}
}
// Initialize on page load
const tracker = new AttributionTracker();
tracker.recordTouch();This class stores up to 10 touchpoints in localStorage, providing first-touch, last-touch, and full journey data at conversion time. You can use this data to evaluate channel effectiveness.
Email campaigns require special tracking to measure open rates, click-through rates, and which links drive the most engagement.
Email Click Tracking
Email tracking URLs wrap destination links with tracking endpoints that log clicks before redirecting. This lets you measure engagement at the recipient and campaign level.
// Email tracking URL builder
function buildEmailTrackingUrl(baseUrl, emailData) {
const url = new URL('/track/email', 'https://yoursite.com');
// Destination
url.searchParams.set('url', baseUrl);
// Email metadata
url.searchParams.set('email_id', emailData.emailId);
url.searchParams.set('recipient_id', emailData.recipientId);
url.searchParams.set('campaign_id', emailData.campaignId);
url.searchParams.set('link_id', emailData.linkId);
return url.toString();
}
// Email tracking redirect handler
app.get('/track/email', async (req, res) => {
const { url, email_id, recipient_id, campaign_id, link_id } = req.query;
// Log the click
await db.emailClicks.create({
emailId: email_id,
recipientId: recipient_id,
campaignId: campaign_id,
linkId: link_id,
clickedAt: new Date(),
userAgent: req.headers['user-agent'],
ip: req.ip
});
// Update email status
await db.emailRecipients.update(
{ id: recipient_id },
{ clicked: true, lastClickedAt: new Date() }
);
// Redirect to destination with tracking
const destination = new URL(url);
destination.searchParams.set('utm_source', 'email');
destination.searchParams.set('utm_medium', 'email');
destination.searchParams.set('utm_campaign', campaign_id);
res.redirect(302, destination.toString());
});Each link in an email gets a unique tracking URL with recipient and campaign identifiers. When clicked, the server logs the engagement and adds UTM parameters to the final destination for analytics integration.
Tracking must balance business needs with user privacy and regulatory compliance. Privacy-respecting implementations maintain measurement capability while respecting user choices.
Privacy Considerations
With GDPR, CCPA, and other privacy regulations, you need to respect user consent and limit tracking when appropriate. Here are patterns for privacy-compliant tracking.
// Privacy-compliant tracking
// Check for consent before tracking
function canTrack() {
return localStorage.getItem('tracking_consent') === 'granted';
}
// Strip tracking params if no consent
function sanitizeUrl(url) {
if (canTrack()) return url;
const sanitized = new URL(url);
const trackingParams = [
'utm_source', 'utm_medium', 'utm_campaign',
'gclid', 'fbclid', 'msclkid',
'ref', 'source', 'click_id'
];
trackingParams.forEach(param => {
sanitized.searchParams.delete(param);
});
return sanitized.toString();
}
// Privacy-first click ID (hashed, not PII)
function generatePrivacyClickId() {
// Use session-based ID, not persistent
const sessionId = sessionStorage.getItem('session_id') ||
(sessionStorage.setItem('session_id', crypto.randomUUID()), sessionStorage.getItem('session_id'));
// Hash for privacy
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(sessionId))
.then(hash => Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 16));
}These functions check for consent before tracking and strip parameters when consent is not granted. Session-based click IDs provide some attribution without persistent tracking.