ScaledByDesign/Insights
ServicesPricingAboutContact
Book a Call
Scaled By Design

Fractional CTO + execution partner for revenue-critical systems.

Company

  • About
  • Services
  • Contact

Resources

  • Insights
  • Pricing
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service

© 2026 ScaledByDesign. All rights reserved.

contact@scaledbydesign.com

On This Page

You're Missing Half Your DataThe Architecture: First-Party, Server-Side, EverythingThe Key InsightBuilding the First-Party Tracking StackLayer 1: Identity ResolutionLayer 2: Event CollectionLayer 3: Server-Side DestinationsLayer 4: Your Own Analytics DatabaseThe Migration ChecklistWeek 1: Set Up First-Party CollectionWeek 2: Add Server-Side DestinationsWeek 3: Validate and CompareWeek 4: Optimize and ExpandThe Results
  1. Insights
  2. Split Testing & Tracking
  3. The Tracking Stack That Survives iOS, Ad Blockers, and Cookie Death

The Tracking Stack That Survives iOS, Ad Blockers, and Cookie Death

February 4, 2026·ScaledByDesign·
trackingprivacyanalyticsserver-side

You're Missing Half Your Data

Run this test right now: compare your Google Analytics session count against your server access logs. If you're like most companies, GA is showing 30-50% fewer sessions than actually occurred.

That gap is growing every year:

  • iOS App Tracking Transparency: 75-85% of users opt out
  • Safari ITP: Caps first-party cookies at 7 days
  • Ad blockers: 30-40% of desktop users block tracking scripts
  • Firefox Enhanced Tracking Protection: Blocks third-party cookies by default
  • Chrome Privacy Sandbox: Third-party cookies deprecated

Your client-side tracking is dying. The question isn't whether to move server-side — it's how fast.

The Architecture: First-Party, Server-Side, Everything

Old architecture (dying):
  Browser → Third-party pixels (blocked)
  Browser → Google Analytics JS (blocked by 30%)
  Browser → Facebook Pixel (blocked by 40%)
  Result: 50-70% data coverage

New architecture (resilient):
  Browser → Your first-party domain → Your server
  Your server → Facebook CAPI
  Your server → Google Measurement Protocol
  Your server → Your analytics database
  Your server → Any other destination
  Result: 95-99% data coverage

The Key Insight

When tracking goes through YOUR domain (first-party), it can't be blocked by ad blockers or privacy tools. The browser sees it as a normal request to your own website.

// Client-side: sends to YOUR domain, not a third party
fetch("/api/track", {
  method: "POST",
  body: JSON.stringify({
    event: "page_view",
    url: window.location.href,
    referrer: document.referrer,
    timestamp: Date.now(),
    sessionId: getSessionId(), // First-party cookie
  }),
});
 
// Server-side: fans out to all destinations
app.post("/api/track", async (req, res) => {
  const event = validateEvent(req.body);
  const userId = resolveIdentity(req);
 
  // Send to all destinations in parallel
  await Promise.allSettled([
    storeInDatabase(event, userId),
    sendToGoogleMP(event, userId),
    sendToFacebookCAPI(event, userId),
    sendToAmplitude(event, userId),
  ]);
 
  res.status(200).send();
});

Building the First-Party Tracking Stack

Layer 1: Identity Resolution

The foundation of everything. Without consistent identity, your data is noise:

function resolveIdentity(req: Request): UserIdentity {
  // Priority order for identity resolution
  const identity: UserIdentity = {
    // Authenticated user (strongest signal)
    userId: req.user?.id || null,
 
    // First-party cookie (persists across sessions)
    deviceId: req.cookies["_device_id"] || generateDeviceId(),
 
    // Session ID (current visit)
    sessionId: req.cookies["_session_id"] || generateSessionId(),
 
    // Hashed email (for ad platform matching)
    hashedEmail: req.user?.email
      ? sha256(req.user.email.toLowerCase().trim())
      : null,
 
    // IP-based fingerprint (fallback, less reliable)
    fingerprint: generateFingerprint(req),
  };
 
  return identity;
}

Cookie strategy:

  • _device_id: First-party, HttpOnly, 1-year expiry, SameSite=Lax
  • _session_id: First-party, HttpOnly, 30-minute sliding expiry
  • Set from your server on your domain — immune to ITP and ad blockers

Layer 2: Event Collection

// Standardized event schema
interface TrackingEvent {
  // Required
  event: string;           // "page_view", "add_to_cart", "purchase"
  timestamp: number;       // Unix ms
  sessionId: string;
 
  // Identity (at least one required)
  userId?: string;
  deviceId?: string;
 
  // Context
  url?: string;
  referrer?: string;
  userAgent?: string;
  ip?: string;             // For geo lookup, then discard
 
  // Event-specific properties
  properties?: Record<string, unknown>;
}
 
// Server-side validation and enrichment
function processEvent(raw: TrackingEvent): EnrichedEvent {
  return {
    ...raw,
    // Enrich with server-side data
    geo: geoLookup(raw.ip),
    device: parseUserAgent(raw.userAgent),
    // Add server timestamp (client clocks can't be trusted)
    serverTimestamp: Date.now(),
    // Deduplicate
    eventId: generateEventId(raw),
  };
}

Layer 3: Server-Side Destinations

// Facebook Conversions API
async function sendToFacebookCAPI(event: EnrichedEvent) {
  if (!["purchase", "add_to_cart", "lead"].includes(event.event)) return;
 
  await fetch(
    `https://graph.facebook.com/v19.0/${FB_PIXEL_ID}/events`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        data: [{
          event_name: mapToFBEvent(event.event),
          event_time: Math.floor(event.timestamp / 1000),
          event_source_url: event.url,
          action_source: "website",
          user_data: {
            em: [event.hashedEmail],
            external_id: [sha256(event.userId || event.deviceId)],
            client_ip_address: event.ip,
            client_user_agent: event.userAgent,
            fbc: event.properties?.fbc,  // Facebook click ID
            fbp: event.properties?.fbp,  // Facebook browser ID
          },
          custom_data: event.properties,
        }],
        access_token: FB_ACCESS_TOKEN,
      }),
    }
  );
}
 
// Google Measurement Protocol (GA4)
async function sendToGoogleMP(event: EnrichedEvent) {
  await fetch(
    `https://www.google-analytics.com/mp/collect?measurement_id=${GA_ID}&api_secret=${GA_SECRET}`,
    {
      method: "POST",
      body: JSON.stringify({
        client_id: event.deviceId,
        user_id: event.userId,
        events: [{
          name: mapToGAEvent(event.event),
          params: {
            ...event.properties,
            session_id: event.sessionId,
            engagement_time_msec: 100,
          },
        }],
      }),
    }
  );
}

Layer 4: Your Own Analytics Database

Don't rely solely on third-party platforms. Store your own data:

CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  event_name VARCHAR(100) NOT NULL,
  user_id VARCHAR(255),
  device_id VARCHAR(255) NOT NULL,
  session_id VARCHAR(255) NOT NULL,
  url TEXT,
  referrer TEXT,
  properties JSONB,
  geo_country VARCHAR(2),
  geo_region VARCHAR(100),
  device_type VARCHAR(50),
  browser VARCHAR(100),
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  server_timestamp TIMESTAMP NOT NULL
);
 
CREATE INDEX idx_events_user ON events (user_id, created_at);
CREATE INDEX idx_events_session ON events (session_id, created_at);
CREATE INDEX idx_events_name ON events (event_name, created_at);

Why own your data: Platforms change APIs, raise prices, and sunset products. Your own event database is the one source of truth you control.

The Migration Checklist

Week 1: Set Up First-Party Collection

  • Create /api/track endpoint on your domain
  • Implement first-party cookie identity
  • Collect page views and key events server-side
  • Run in parallel with existing client-side tracking

Week 2: Add Server-Side Destinations

  • Facebook CAPI (with event deduplication)
  • Google Measurement Protocol
  • Store events in your own database

Week 3: Validate and Compare

  • Compare server-side vs client-side event counts
  • Verify identity resolution across sessions
  • Check ad platform match rates (should improve)

Week 4: Optimize and Expand

  • Add remaining event types
  • Remove redundant client-side pixels
  • Build basic analytics queries against your database
  • Set up data quality monitoring

The Results

MetricClient-Side OnlyFirst-Party Server-Side
Event capture rate55-70%95-99%
Cross-session identity40-60%85-95%
Ad platform match rate30-50%70-85%
Data latencyReal-timeReal-time
Resilience to privacy changesLowHigh
Data ownershipPlatform-dependentYou own it

The tracking landscape will keep getting more restrictive. Every privacy regulation, browser update, and OS change will erode client-side tracking further. The companies that invest in first-party, server-side infrastructure now will have a compounding data advantage over those that keep patching client-side pixels.

Build the stack that survives. Your future self will thank you.

Previous
The AI Implementation Playbook for Non-Technical Founders
Next
How to Cut Your LLM Costs by 70% Without Losing Quality
Insights
A/B Testing Is Lying to You — Statistical Significance Isn't EnoughServer-Side Split Testing: Why Client-Side Tools Are Costing You RevenueThe Tracking Stack That Survives iOS, Ad Blockers, and Cookie DeathHow to Run Pricing Experiments Without Destroying TrustYour Conversion Rate Is a Vanity Metric — Here's What to Track InsteadBuilding a Feature Flag System That Doesn't Become Technical DebtThe Data Layer Architecture That Makes Every Test Trustworthy

Ready to Ship?

Let's talk about your engineering challenges and how we can help.

Book a Call