What We're Tracking
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
Growth Package Interest
Scale Package Interest
Tracking Architecture
Data Flow Overview
Google Ads
Traffic SourceLanding Page
comcreate.org/growGTM Container
Event CaptureData Layer
Event DataGA4
AnalyticsGoogle Ads
ConversionsCal.com Webhook
Booking DataN8N Workflow
ProcessingCRM
Lead StorageUTM Parameter Strategy
| Parameter | Example Value | Purpose |
|---|---|---|
utm_source |
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
<!-- 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 -->
<!-- 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 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
The Data Layer MUST be initialized BEFORE the GTM container code loads. Place this script at the very top of your <head> section.
// 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
// 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 Type | Conversion Linker |
| Enable linking on all page URLs | Checked |
| Trigger | All Pages |
| Tag Priority | 100 (fires before other tags) |
Tag 2: Discovery Call Conversion
| Tag Type | Google Ads Conversion Tracking |
| Conversion ID | AW-XXXXXXXXX |
| Conversion Label | DISCOVERY_CALL_LABEL |
| Conversion Value | {{DLV - Lead Value}} |
| Transaction ID | {{DLV - Transaction ID}} |
| Currency Code | USD |
| Trigger | Discovery Call Booked |
Tag 3: Quote Request Conversion
| Tag Type | Google Ads Conversion Tracking |
| Conversion ID | AW-XXXXXXXXX |
| Conversion Label | QUOTE_REQUEST_LABEL |
| Conversion Value | {{DLV - Lead Value}} |
| Transaction ID | {{DLV - Transaction ID}} |
| Currency Code | USD |
| Trigger | Quote Request Submitted |
Step 3: GA4 Event Tags
GA4 Configuration Tag
| Tag Type | Google Analytics: GA4 Configuration |
| Measurement ID | G-XXXXXXXXXX |
| Trigger | All Pages |
GA4 - Generate Lead Event
| Tag Type | Google Analytics: GA4 Event |
| Event Name | generate_lead |
| Parameters |
currency: USDvalue: {{DLV - Lead Value}}lead_source: landing_pagepackage_interest: {{DLV - Package Selected}}business_type: {{DLV - Business Type}}
|
| Trigger | Quote Request Submitted OR Discovery Call Booked |
Multi-Step Form Tracking
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 |
// 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
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.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:
{
"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:
- Receive the Cal.com webhook
- Extract lead data and UTM parameters
- Create/update contact in CRM (HubSpot, Airtable, etc.)
- Send confirmation email via SendGrid/Mailchimp
- Send Google Ads offline conversion (optional)
- Notify sales team via Slack/email
Enhanced Conversions Setup
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
Enable Enhanced Conversions in Google Ads
Go to Google Ads -> Tools & Settings -> Conversions -> Select your conversion -> Enhanced conversions -> Turn on
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
Variable Mapping
| Field | Data Layer Variable |
|---|---|
| {{DLV - User Email}} | |
| Phone Number | {{DLV - User Phone}} |
| First Name | {{DLV - User First Name}} |
Data Layer Structure for Enhanced Conversions
// 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'
}
}
});
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
Open GTM Preview
In GTM, click "Preview" -> Enter your landing page URL -> Click "Connect"
Verify Page Load Tags
Check "Tags Fired" panel. Confirm these fire on page load:
- GA4 Configuration
- Conversion Linker
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
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
// 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:
page_viewevents as you navigateform_startwhen interacting with formgenerate_leadon form submission- Event parameters populated correctly
Google Ads Conversion Verification
- Add
?gclid=test_click_123to your landing page URL - Complete a test conversion (form submission or booking)
- Wait 24-48 hours
- Check Google Ads -> Tools -> Conversions -> Status should show "Recording conversions"