Back to Blueprints
Developer Implementation Guide

Tracking Blueprint

Complete Google Analytics and Conversion Tracking for ComCreate's B2B Sales Landing Page

GA4 GTM Google Ads Enhanced Conversions Cal.com

What We're Tracking

Purpose of This Document

This guide covers tracking implementation for ComCreate's B2B sales landing page - where local service business owners (plumbers, HVAC, electricians, roofers, dentists, lawyers) land from Google Ads to learn about and purchase ComCreate's marketing packages ($749/$1,099/$1,499/month).

Primary Conversion Events

Event Type Estimated Value Priority
Discovery Call Booked Primary Conversion $150 (avg. close rate x LTV) Critical
Quote Request Submitted Primary Conversion $100 Critical
Package Selected (Form Step 2) Micro-conversion $25 Important
Form Started Engagement $0 Observation
Pricing Section Viewed Engagement $0 Observation
Testimonial Video Played Engagement $0 Observation

Lead Value by Package Interest

Starter Package Interest

$749/mo
Lead Value: $75 (10% close rate x 1 month)

Growth Package Interest

$1,099/mo
Lead Value: $110 - Most common selection

Scale Package Interest

$1,499/mo
Lead Value: $150

Tracking Architecture

Data Flow Overview

Google Ads
Traffic Source
->
Landing Page
comcreate.org/grow
->
GTM Container
Event Capture
Data Layer
Event Data
->
GA4
Analytics
->
Google Ads
Conversions
Cal.com Webhook
Booking Data
->
N8N Workflow
Processing
->
CRM
Lead Storage

UTM Parameter Strategy

Parameter Example Value Purpose
utm_source google Traffic source identification
utm_medium cpc Paid vs organic distinction
utm_campaign b2b_plumbers_austin Campaign identification
utm_content growth_package_ad Ad creative tracking
utm_term plumber+marketing+services Keyword tracking
gclid [auto-generated] Google Ads click ID

Google Tag Manager Setup

Step 1: Install GTM Container

Add to <head> (as high as possible)
<!-- Google Tag Manager -->
<script>
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');
</script>
<!-- End Google Tag Manager -->
Add immediately after <body> tag
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
Replace GTM-XXXXXXX

Replace GTM-XXXXXXX with your actual GTM Container ID from tagmanager.google.com

Step 2: Create Triggers in GTM

Trigger Name Type Configuration
All Pages Page View All Page Views
Form Started Custom Event Event equals form_start
Package Selected Custom Event Event equals package_selected
Quote Request Submitted Custom Event Event equals quote_request
Discovery Call Booked Custom Event Event equals discovery_call_booked
Pricing Section Viewed Custom Event Event equals pricing_viewed
Video Played Custom Event Event equals testimonial_video_play
Scroll 50% Scroll Depth Vertical Scroll Depth 50%
Scroll 90% Scroll Depth Vertical Scroll Depth 90%

Step 3: Create Data Layer Variables

Variable Name Data Layer Variable Name Purpose
DLV - Business Type business_type plumber, hvac, electrician, etc.
DLV - Package Selected package_selected starter, growth, scale
DLV - Lead Value lead_value Dynamic value based on package
DLV - Form Step form_step 1, 2, or 3
DLV - User Email user_data.email For Enhanced Conversions
DLV - User Phone user_data.phone For Enhanced Conversions
DLV - Business Name user_data.business_name Lead qualification
DLV - Transaction ID transaction_id Deduplication

Data Layer Implementation

Critical Requirement

The Data Layer MUST be initialized BEFORE the GTM container code loads. Place this script at the very top of your <head> section.

data-layer-init.js - Place BEFORE GTM script
// ComCreate B2B Landing Page - Data Layer Initialization
// Must load BEFORE Google Tag Manager

window.dataLayer = window.dataLayer || [];

// Helper: Get URL parameter value
function getUrlParam(name) {
  return new URLSearchParams(window.location.search).get(name);
}

// Helper: Get or create session ID
function getSessionId() {
  let sessionId = sessionStorage.getItem('cc_session_id');
  if (!sessionId) {
    sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    sessionStorage.setItem('cc_session_id', sessionId);
  }
  return sessionId;
}

// Store GCLID in cookie for cross-session attribution (90 days)
(function() {
  const gclid = getUrlParam('gclid');
  if (gclid) {
    const expiry = new Date();
    expiry.setDate(expiry.getDate() + 90);
    document.cookie = 'gclid=' + gclid + ';expires=' + expiry.toUTCString() + ';path=/;SameSite=Lax';
  }
})();

// Store all UTM parameters in sessionStorage
(function() {
  const utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
  utmParams.forEach(param => {
    const value = getUrlParam(param);
    if (value) {
      sessionStorage.setItem(param, value);
    }
  });
})();

// Get stored or current UTM value
function getUtm(param) {
  return getUrlParam(param) || sessionStorage.getItem(param) || 'direct';
}

// Push initial page data when DOM is ready
dataLayer.push({
  'event': 'page_data_ready',
  'page': {
    'type': 'b2b_sales_landing',
    'path': window.location.pathname,
    'title': document.title,
    'referrer': document.referrer
  },
  'session': {
    'id': getSessionId(),
    'landing_page': sessionStorage.getItem('landing_page') || window.location.href
  },
  'campaign': {
    'source': getUtm('utm_source'),
    'medium': getUtm('utm_medium'),
    'campaign': getUtm('utm_campaign'),
    'content': getUtm('utm_content'),
    'term': getUtm('utm_term'),
    'gclid': getUrlParam('gclid') || document.cookie.match(/gclid=([^;]+)/)?.[1] || 'none'
  }
});

// Store landing page on first visit
if (!sessionStorage.getItem('landing_page')) {
  sessionStorage.setItem('landing_page', window.location.href);
}

// Track page load time for engagement metrics
window.pageLoadTime = Date.now();

console.log('[ComCreate] Data layer initialized');

Lead Value Configuration

lead-value-config.js
// Dynamic Lead Value Calculator
// Based on package selection and business type

const ComCreateLeadValues = {
  // Base values by package tier
  packages: {
    'starter': 75,    // $749/mo x 10% close rate
    'growth': 110,    // $1,099/mo x 10% close rate (most common)
    'scale': 150      // $1,499/mo x 10% close rate
  },

  // Multipliers by business type (based on average deal size)
  businessMultipliers: {
    'plumber': 1.0,
    'hvac': 1.2,        // Higher ticket services
    'electrician': 1.0,
    'roofer': 1.3,      // High ticket, longer cycles
    'dentist': 1.5,     // High LTV
    'lawyer': 1.4,      // High LTV
    'other': 1.0
  },

  // Discovery call booked is worth more (higher intent)
  discoveryCallBonus: 1.5
};

/**
 * Calculate lead value based on package and business type
 * @param {string} packageTier - 'starter', 'growth', or 'scale'
 * @param {string} businessType - Type of business
 * @param {boolean} isDiscoveryCall - Whether they booked a call
 * @returns {number} Lead value in dollars
 */
function calculateLeadValue(packageTier, businessType, isDiscoveryCall = false) {
  const baseValue = ComCreateLeadValues.packages[packageTier] || 100;
  const multiplier = ComCreateLeadValues.businessMultipliers[businessType] || 1.0;
  const callBonus = isDiscoveryCall ? ComCreateLeadValues.discoveryCallBonus : 1.0;

  return Math.round(baseValue * multiplier * callBonus);
}

/**
 * Generate unique transaction ID for deduplication
 * @returns {string} Unique transaction ID
 */
function generateTransactionId() {
  return 'cc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}

Google Ads Conversion Setup

Step 1: Create Conversion Actions in Google Ads

Go to Google Ads -> Tools & Settings -> Measurement -> Conversions -> + New conversion action -> Website

Discovery Call Booked

Category: Book appointment
Value: Use different values for each conversion
Default: $150
Count: One per click
Attribution: Data-driven (or Position-based)
Window: 30 days click, 7 days view

Quote Request Submitted

Category: Submit lead form
Value: Use different values for each conversion
Default: $100
Count: One per click
Attribution: Data-driven
Window: 30 days click, 7 days view

Package Selected (Micro)

Category: Other
Value: $25
Count: One per click
Primary: No (observation only)
Use for: Smart Bidding signal

Step 2: Create GTM Tags for Google Ads

Tag 1: Conversion Linker (REQUIRED)

Tag TypeConversion Linker
Enable linking on all page URLsChecked
TriggerAll Pages
Tag Priority100 (fires before other tags)

Tag 2: Discovery Call Conversion

Tag TypeGoogle Ads Conversion Tracking
Conversion IDAW-XXXXXXXXX
Conversion LabelDISCOVERY_CALL_LABEL
Conversion Value{{DLV - Lead Value}}
Transaction ID{{DLV - Transaction ID}}
Currency CodeUSD
TriggerDiscovery Call Booked

Tag 3: Quote Request Conversion

Tag TypeGoogle Ads Conversion Tracking
Conversion IDAW-XXXXXXXXX
Conversion LabelQUOTE_REQUEST_LABEL
Conversion Value{{DLV - Lead Value}}
Transaction ID{{DLV - Transaction ID}}
Currency CodeUSD
TriggerQuote Request Submitted

Step 3: GA4 Event Tags

GA4 Configuration Tag

Tag TypeGoogle Analytics: GA4 Configuration
Measurement IDG-XXXXXXXXXX
TriggerAll Pages

GA4 - Generate Lead Event

Tag TypeGoogle Analytics: GA4 Event
Event Namegenerate_lead
Parameters currency: USD
value: {{DLV - Lead Value}}
lead_source: landing_page
package_interest: {{DLV - Package Selected}}
business_type: {{DLV - Business Type}}
TriggerQuote Request Submitted OR Discovery Call Booked

Multi-Step Form Tracking

Why Multi-Step?

Multi-step forms convert 743% better than single-step forms. We track each step to identify where prospects drop off and optimize accordingly.

Form Structure

Step Fields Event to Fire Purpose
Step 1 Business type, Location form_start Low friction entry, qualify lead type
Step 2 Package selection, Monthly budget package_selected Intent signal, qualification
Step 3 Name, Email, Phone quote_request Capture lead info
multi-step-form-tracking.js
// ComCreate Multi-Step Form Tracking
// Tracks progression through the lead capture form

const FormTracker = {
  formData: {},
  currentStep: 0,
  formStarted: false,

  init: function() {
    const form = document.querySelector('#lead-capture-form');
    if (!form) {
      console.warn('[FormTracker] Form not found');
      return;
    }

    this.form = form;
    this.bindEvents();
    console.log('[FormTracker] Initialized');
  },

  bindEvents: function() {
    // Track form start (first interaction)
    this.form.querySelectorAll('input, select').forEach(field => {
      field.addEventListener('focus', () => this.trackFormStart(), { once: true });
    });

    // Track step progression
    this.form.querySelectorAll('[data-step-trigger]').forEach(btn => {
      btn.addEventListener('click', (e) => this.trackStepCompletion(e));
    });

    // Track final submission
    this.form.addEventListener('submit', (e) => this.trackSubmission(e));
  },

  trackFormStart: function() {
    if (this.formStarted) return;
    this.formStarted = true;

    dataLayer.push({
      'event': 'form_start',
      'form_id': 'lead_capture',
      'form_step': 1,
      'time_to_engage': Math.round((Date.now() - window.pageLoadTime) / 1000)
    });

    console.log('[FormTracker] Form started');
  },

  trackStepCompletion: function(e) {
    const trigger = e.target.closest('[data-step-trigger]');
    const nextStep = parseInt(trigger.dataset.stepTrigger);

    // Collect data from current step
    this.collectStepData(this.currentStep);

    // Step 2 completion = package selected
    if (nextStep === 3 && this.formData.package_selected) {
      const leadValue = calculateLeadValue(
        this.formData.package_selected,
        this.formData.business_type,
        false
      );

      dataLayer.push({
        'event': 'package_selected',
        'form_step': 2,
        'package_selected': this.formData.package_selected,
        'business_type': this.formData.business_type,
        'lead_value': leadValue
      });

      console.log('[FormTracker] Package selected:', this.formData.package_selected);
    }

    this.currentStep = nextStep;
  },

  collectStepData: function(step) {
    const stepContainer = this.form.querySelector(`[data-step="${step}"]`);
    if (!stepContainer) return;

    stepContainer.querySelectorAll('input, select').forEach(field => {
      if (field.name && field.value) {
        this.formData[field.name] = field.value;
      }
    });
  },

  trackSubmission: function(e) {
    e.preventDefault();

    // Collect final step data
    this.collectStepData(3);

    const transactionId = generateTransactionId();
    const leadValue = calculateLeadValue(
      this.formData.package_selected || 'growth',
      this.formData.business_type || 'other',
      false
    );

    // Push conversion event
    dataLayer.push({
      'event': 'quote_request',
      'form_id': 'lead_capture',
      'transaction_id': transactionId,
      'lead_value': leadValue,
      'package_selected': this.formData.package_selected,
      'business_type': this.formData.business_type,
      'user_data': {
        'email': this.normalizeEmail(this.formData.email),
        'phone_number': this.normalizePhone(this.formData.phone),
        'business_name': this.formData.business_name
      }
    });

    console.log('[FormTracker] Quote request submitted', { transactionId, leadValue });

    // Submit to backend
    this.submitToBackend(transactionId);
  },

  normalizeEmail: function(email) {
    return email ? email.toLowerCase().trim() : '';
  },

  normalizePhone: function(phone) {
    if (!phone) return '';
    const cleaned = phone.replace(/\D/g, '');
    return cleaned.length === 10 ? '+1' + cleaned : '+' + cleaned;
  },

  submitToBackend: function(transactionId) {
    const payload = {
      ...this.formData,
      transaction_id: transactionId,
      utm_source: sessionStorage.getItem('utm_source') || 'direct',
      utm_medium: sessionStorage.getItem('utm_medium') || 'none',
      utm_campaign: sessionStorage.getItem('utm_campaign') || 'none',
      gclid: document.cookie.match(/gclid=([^;]+)/)?.[1] || 'none',
      landing_page: sessionStorage.getItem('landing_page'),
      submitted_at: new Date().toISOString()
    };

    fetch('/api/leads', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    })
    .then(response => {
      if (response.ok) {
        // Show success state
        this.form.style.display = 'none';
        document.querySelector('#form-success')?.classList.add('visible');
      }
    })
    .catch(error => {
      console.error('[FormTracker] Submission error:', error);
    });
  }
};

// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => FormTracker.init());

Cal.com Booking Integration

Why Calendar Integration Matters

66.7% of leads who book a discovery call convert to customers. Tracking calendar bookings is critical for measuring true conversion intent and optimizing Google Ads bidding.

Cal.com Embed Tracking

cal-booking-tracking.js
// Cal.com Booking Event Tracking
// Listens for Cal.com postMessage events

(function() {
  // Store form data from previous steps for attribution
  let preBookingData = {};

  // Capture pre-booking data from sessionStorage
  function getPreBookingData() {
    return {
      package_selected: sessionStorage.getItem('cc_package_selected') || 'growth',
      business_type: sessionStorage.getItem('cc_business_type') || 'other',
      utm_source: sessionStorage.getItem('utm_source') || 'direct',
      utm_campaign: sessionStorage.getItem('utm_campaign') || 'none',
      gclid: document.cookie.match(/gclid=([^;]+)/)?.[1] || 'none'
    };
  }

  // Listen for Cal.com booking events
  window.addEventListener('message', function(event) {
    // Verify origin (adjust domain as needed)
    if (!event.origin.includes('cal.com')) return;

    const data = event.data;

    // Cal.com sends different event types
    if (data.event === 'booking:confirmed' || data.type === 'booking_successful') {
      handleBookingConfirmed(data);
    }

    if (data.event === 'booking:created') {
      handleBookingConfirmed(data);
    }
  });

  function handleBookingConfirmed(calData) {
    const preData = getPreBookingData();
    const transactionId = generateTransactionId();

    const leadValue = calculateLeadValue(
      preData.package_selected,
      preData.business_type,
      true // Discovery call bonus
    );

    // Push conversion event
    dataLayer.push({
      'event': 'discovery_call_booked',
      'transaction_id': transactionId,
      'lead_value': leadValue,
      'package_selected': preData.package_selected,
      'business_type': preData.business_type,
      'booking_data': {
        'event_type': calData.eventType || 'discovery-call',
        'start_time': calData.startTime || null,
        'attendee_email': calData.attendeeEmail || null
      },
      'user_data': {
        'email': calData.attendeeEmail || null,
        'first_name': calData.attendeeName?.split(' ')[0] || null
      }
    });

    console.log('[CalTracking] Discovery call booked', {
      transactionId,
      leadValue,
      package: preData.package_selected
    });
  }
})();

Cal.com Webhook for Server-Side Tracking

For more reliable tracking, set up a webhook in Cal.com that sends booking data to your N8N workflow:

Cal.com Webhook Payload Example
{
  "triggerEvent": "BOOKING_CREATED",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "payload": {
    "type": "discovery-call",
    "title": "Discovery Call with ComCreate",
    "startTime": "2024-01-16T14:00:00.000Z",
    "endTime": "2024-01-16T14:30:00.000Z",
    "attendees": [
      {
        "email": "owner@plumbingco.com",
        "name": "John Smith",
        "timeZone": "America/Chicago"
      }
    ],
    "metadata": {
      "package_interest": "growth",
      "business_type": "plumber",
      "utm_source": "google",
      "utm_campaign": "b2b_plumbers_austin",
      "gclid": "CjwKCAiA..."
    }
  }
}

N8N Webhook Handler

Your N8N workflow should:

  1. Receive the Cal.com webhook
  2. Extract lead data and UTM parameters
  3. Create/update contact in CRM (HubSpot, Airtable, etc.)
  4. Send confirmation email via SendGrid/Mailchimp
  5. Send Google Ads offline conversion (optional)
  6. Notify sales team via Slack/email

Enhanced Conversions Setup

Why Enhanced Conversions?

Enhanced Conversions improve conversion measurement accuracy by 5-15% by sending hashed (SHA-256) user data to Google. This is especially important for B2B where conversion paths are longer.

Implementation in GTM

1

Enable Enhanced Conversions in Google Ads

Go to Google Ads -> Tools & Settings -> Conversions -> Select your conversion -> Enhanced conversions -> Turn on

2

Configure in GTM Conversion Tag

In your Google Ads conversion tag:
1. Check "Include user-provided data from your website"
2. Select "Code" or "Manual configuration"
3. Map the data layer variables

3

Variable Mapping

FieldData Layer Variable
Email{{DLV - User Email}}
Phone Number{{DLV - User Phone}}
First Name{{DLV - User First Name}}

Data Layer Structure for Enhanced Conversions

Enhanced conversions data push
// Push with conversion event
dataLayer.push({
  'event': 'quote_request',
  'transaction_id': 'cc_1705123456789_abc123',
  'lead_value': 110,

  // Enhanced Conversions user data
  // GTM will automatically hash these values
  'user_data': {
    'email': 'owner@plumbingcompany.com',  // Will be hashed
    'phone_number': '+15125551234',         // E.164 format, will be hashed
    'address': {
      'first_name': 'John',
      'last_name': 'Smith',
      'city': 'Austin',
      'region': 'TX',
      'postal_code': '78701',
      'country': 'US'
    }
  }
});
Privacy Note

Google automatically hashes user data using SHA-256 before transmission. The raw data never leaves the user's browser. This complies with privacy regulations when properly disclosed in your privacy policy.

Testing & Validation

GTM Preview Mode Testing

1

Open GTM Preview

In GTM, click "Preview" -> Enter your landing page URL -> Click "Connect"

2

Verify Page Load Tags

Check "Tags Fired" panel. Confirm these fire on page load:
- GA4 Configuration
- Conversion Linker

3

Test Form Progression

Fill out the multi-step form and verify each event:
- form_start fires on first field focus
- package_selected fires when moving to step 3
- quote_request fires on submit

4

Verify Data Layer Values

Click on each event in Preview and check the "Data Layer" tab. Confirm:
- lead_value is calculated correctly
- transaction_id is unique
- user_data is populated

Debug Mode Script

Add to page for development debugging
// Enable debug mode with ?debug=true in URL
if (new URLSearchParams(location.search).has('debug')) {
  // Log all dataLayer pushes
  const originalPush = dataLayer.push.bind(dataLayer);
  dataLayer.push = function(...args) {
    console.log(
      '%c[dataLayer.push]',
      'color: #1873FA; font-weight: bold; font-size: 12px;',
      args[0]
    );
    return originalPush(...args);
  };

  console.log('%c[ComCreate Debug Mode Enabled]', 'color: #1873FA; font-weight: bold;');
  console.log('Current dataLayer:', dataLayer);
}

GA4 Real-Time Verification

Go to GA4 -> Reports -> Realtime while testing. You should see:

Google Ads Conversion Verification

  1. Add ?gclid=test_click_123 to your landing page URL
  2. Complete a test conversion (form submission or booking)
  3. Wait 24-48 hours
  4. Check Google Ads -> Tools -> Conversions -> Status should show "Recording conversions"

Implementation Checklist

GTM Container Setup

Data Layer

Form Tracking

Calendar Booking

Google Ads Conversions

GA4 Setup

Testing Complete