Services

Full-Stack Development, Data Engineering

Industry

B2B Marketplace

Year

2024

Full-stack campaign attribution, zero third-party SDKs.

A B2B marketplace was spending heavily on paid acquisition across Google, LinkedIn, and industry portals - but had no reliable way to trace a signup back to the ad that drove it. Third-party analytics tools were either blocked by ad blockers, incompatible with GDPR requirements, or too coarse to distinguish between initial and most-recent touchpoints. They needed attribution they could trust, built into the platform itself.

01.
THE CHALLENGE

Analytics That Couldn't Be Trusted

The client relied on Google Analytics for campaign tracking. The problems were immediate: ad blockers wiped out 30-40% of tracking data, GDPR consent banners further reduced coverage, and the data that did arrive couldn't be tied to actual signups in the CRM. The marketing team was making budget decisions on incomplete data. Anonymous visitors who browsed for days before signing up lost all attribution context - the system had no memory of where they originally came from.

30-40% of all tracking data was silently erased by ad blockers. The marketing team was making six-figure budget decisions on incomplete numbers.

02.
THE SOLUTION

A Dual-Layer Attribution Architecture

I designed a two-layer system that works entirely within the application boundary. On the frontend, a lightweight JavaScript class called CTracker intercepts UTM-style parameters (prefixed as c_source, c_medium, c_campaign) from the URL on first visit, then persists them in localStorage with a dual-slot model: one slot for the initial touchpoint, one for the most recent. No cookies, no external requests. On the backend, a chain of Django middleware - CTrackMiddleware followed by LeadSourceMiddleware - reads these parameters from query strings and session data, validates them, and writes them as LeadSource records in the database. When an anonymous visitor eventually signs up or logs in, a Django signal retroactively binds all stored attribution data to their user account, so nothing gets lost.

The frontend CTracker class captures and persists UTM parameters in localStorage:

JavaScript
class CTracker {
  constructor () {
    this.storageAvailable = this._testStorage();
    if (!this.storageAvailable) return;

    const params = new URLSearchParams(window.location.search);
    const keys   = ['c_source', 'c_medium', 'c_campaign', 'c_term'];

    keys.forEach(key => {
      const val = params.get(key);
      if (!val) return;

      // Always update the "current" touchpoint
      localStorage.setItem(key, val);

      // Only set "initial" once — never overwrite
      if (!localStorage.getItem(key + '_initial')) {
        localStorage.setItem(key + '_initial', val);
      }
    });
  }

  _testStorage () {
    try {
      localStorage.setItem('__ct', '1');
      localStorage.removeItem('__ct');
      return true;
    } catch { return false; }
  }
}

The Attribution Pipeline

Hover over each node to see how data flows through the system, from the first ad click to the final CRM record.

Ad Click

User clicks an ad with c_source, c_medium, and c_campaign URL parameters.

CTracker

Frontend JavaScript intercepts UTM params and persists them in localStorage with initial/current dual-slot model.

Middleware

Django CTrackMiddleware reads URL params and session data, validates against known sources.

LeadSource

LeadSourceMiddleware writes validated attribution data as LeadSource records in PostgreSQL.

User Signup

On login/signup, a Django signal retroactively binds all anonymous attribution to the authenticated user.

  1. 01Ad ClickUser clicks an ad with c_source, c_medium, and c_campaign URL parameters.
  2. 02CTrackerFrontend JavaScript intercepts UTM params and persists them in localStorage with initial/current dual-slot model.
  3. 03MiddlewareDjango CTrackMiddleware reads URL params and session data, validates against known sources.
  4. 04LeadSourceLeadSourceMiddleware writes validated attribution data as LeadSource records in PostgreSQL.
  5. 05User SignupOn login/signup, a Django signal retroactively binds all anonymous attribution to the authenticated user.

The Django middleware chain extracts, validates, and stores attribution data on every request:

Python
class LeadSourceMiddleware:
    """
    Reads c_* attribution params from query string + session,
    validates them, and persists a LeadSource record.
    """
    def __call__(self, request):
        params = self._extract(request)
        if not params:
            return self.get_response(request)

        source, created = LeadSource.objects.get_or_create(
            session_key=request.session.session_key,
            defaults=params,
        )

        # Bind to user when they authenticate
        if request.user.is_authenticated and not source.user:
            source.user = request.user
            source.save(update_fields=['user'])

        return self.get_response(request)
03.
THE RESULT

Complete Attribution, Zero Dependencies

Once deployed, the marketing team had end-to-end visibility for the first time. Every CRM record now carried its full acquisition history: initial source, most recent source, and the entire chain of touchpoints. Ad blocker interference dropped to zero because nothing external was loaded. GDPR compliance was straightforward: no third-party cookies, no cross-site tracking, no consent banner required for first-party analytics. The system processed attribution for thousands of daily visitors with less than 250ms of added latency per request.

KEY METRICS

0%Data Recovered
0%Attribution Coverage
0msAdded Latency
WHAT THE CLIENT SAYS

"We finally know exactly where every signup comes from. No more guessing, no more gaps from ad blockers. Our marketing budget decisions are based on real data for the first time."

Head of Marketing

B2B Marketplace · Enterprise SaaS

FAQ

Why not just use Google Analytics or Segment?

How does attribution survive across anonymous sessions?

What happens if localStorage is unavailable?

TECHNOLOGY STACK