CRM Integration
Connect Google Analytics 4 with your CRM (HubSpot, Salesforce, Pipedrive) for unified customer analytics and attribution.
Why Integrate CRM with Analytics?
Benefits
| Benefit | Description | |---------|-------------| | Full-funnel visibility | Website → Lead → Opportunity → Customer | | Closed-loop reporting | Tie revenue back to marketing sources | | Lead quality insights | Which sources drive best customers | | Sales enablement | Behavior data for sales teams | | Offline conversion import | Track post-website activities |
Data Flow Architecture
┌─────────────────────────────────────────────────────────┐
│ Website/App │
│ │ │
│ ▼ │
│ GA4 / GTM │
│ (client_id, gclid, user behavior) │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ CRM │
│ (Lead/Contact records) │
│ │ │
│ ┌───────────────────┴───────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ Lead Stage Changes Revenue Data │
│ │ │ │
└────┼───────────────────────────────────────┼────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ GA4 / Google Ads (Offline Conversions) │
└─────────────────────────────────────────────────────────┘
HubSpot Integration
Method 1: Native HubSpot Tracking + GA4
Run both tracking systems in parallel:
// Data layer event for both systems
dataLayer.push({
event: 'form_submit',
form_name: 'contact_sales',
// GA4 parameters
lead_type: 'sales_inquiry',
lead_value: 100,
// Include GA4 client_id for HubSpot hidden field
ga_client_id: getGA4ClientId()
});
// HubSpot form with hidden GA4 field
hbspt.forms.create({
portalId: 'YOUR_PORTAL_ID',
formId: 'YOUR_FORM_ID',
onFormReady: function(form) {
// Populate hidden fields
form.querySelector('input[name="ga_client_id"]').value = getGA4ClientId();
form.querySelector('input[name="gclid"]').value = getGclid();
}
});
Method 2: Server-Side Sync
Use HubSpot Workflows + Webhooks:
- Create Workflow triggered on lifecycle stage change
- Send Webhook to your server
- Server processes and sends to GA4 Measurement Protocol
// Server endpoint receiving HubSpot webhook
app.post('/hubspot-webhook', async (req, res) => {
const { email, lifecycleStage, dealAmount, ga_client_id } = req.body;
// Send to GA4 Measurement Protocol
await fetch('https://www.google-analytics.com/mp/collect', {
method: 'POST',
body: JSON.stringify({
client_id: ga_client_id,
events: [{
name: 'crm_stage_change',
params: {
stage: lifecycleStage,
value: dealAmount
}
}]
})
});
res.status(200).send('OK');
});
HubSpot Custom Properties
Create these properties in HubSpot:
| Property | Type | Purpose | |----------|------|---------| | ga_client_id | Single-line text | GA4 anonymous ID | | gclid | Single-line text | Google Ads click ID | | first_touch_source | Single-line text | Attribution | | first_touch_medium | Single-line text | Attribution | | first_touch_campaign | Single-line text | Attribution |
Salesforce Integration
Setup Options
| Method | Best For | Complexity | |--------|----------|------------| | Web-to-Lead with hidden fields | Simple forms | Low | | Salesforce Marketing Cloud | Enterprise | High | | Custom Apex + GA4 MP | Full control | Medium | | Third-party (Segment, etc.) | Existing CDP | Medium |
Web-to-Lead Integration
<!-- Salesforce Web-to-Lead with GA4 data -->
<form action="https://webto.salesforce.com/servlet/servlet.WebToLead" method="POST">
<input type="hidden" name="oid" value="YOUR_ORG_ID">
<input type="hidden" name="retURL" value="https://yoursite.com/thank-you">
<!-- Standard fields -->
<input type="text" name="first_name" placeholder="First Name">
<input type="text" name="last_name" placeholder="Last Name">
<input type="email" name="email" placeholder="Email">
<!-- GA4 hidden fields (custom Salesforce fields) -->
<input type="hidden" id="ga_client_id" name="00N..." value="">
<input type="hidden" id="gclid" name="00N..." value="">
<input type="hidden" id="utm_source" name="00N..." value="">
<input type="hidden" id="utm_medium" name="00N..." value="">
<button type="submit">Submit</button>
</form>
<script>
// Populate hidden fields
document.getElementById('ga_client_id').value = getGA4ClientId();
document.getElementById('gclid').value = getGclid() || '';
document.getElementById('utm_source').value = getUTMParam('utm_source') || '';
document.getElementById('utm_medium').value = getUTMParam('utm_medium') || '';
</script>
Salesforce Offline Conversion Import
Create Apex class to send to GA4:
// Apex class for GA4 integration
public class GA4OfflineConversion {
@future(callout=true)
public static void sendConversion(String clientId, String eventName, Decimal value) {
String measurementId = 'G-XXXXXXXXXX';
String apiSecret = 'YOUR_API_SECRET';
String endpoint = 'https://www.google-analytics.com/mp/collect?measurement_id='
+ measurementId + '&api_secret=' + apiSecret;
Map<String, Object> payload = new Map<String, Object>{
'client_id' => clientId,
'events' => new List<Map<String, Object>>{
new Map<String, Object>{
'name' => eventName,
'params' => new Map<String, Object>{
'value' => value,
'currency' => 'USD'
}
}
}
};
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint);
request.setMethod('POST');
request.setBody(JSON.serialize(payload));
HttpResponse response = http.send(request);
}
}
// Trigger on Opportunity stage change
trigger OpportunityStageChange on Opportunity (after update) {
for (Opportunity opp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(opp.Id);
if (opp.StageName == 'Closed Won' && oldOpp.StageName != 'Closed Won') {
// Get related Lead's GA client_id
String clientId = opp.Lead__r.GA_Client_ID__c;
if (clientId != null) {
GA4OfflineConversion.sendConversion(clientId, 'closed_won', opp.Amount);
}
}
}
}
Pipedrive Integration
Using Pipedrive API + Zapier
- Capture GA4 data at form submission
- Create Person/Deal via Pipedrive API
- Trigger Zap on deal stage change
- Send to GA4 Measurement Protocol
Custom Fields Setup
Create in Pipedrive:
- Person field:
ga_client_id - Person field:
gclid - Person field:
utm_source - Person field:
utm_medium
Zapier Integration
Trigger: Pipedrive - Deal Stage Changed
Filter: Stage = "Won"
Action: Webhooks by Zapier - POST Request
URL: https://www.google-analytics.com/mp/collect?measurement_id=G-XXX&api_secret=YYY
Body: {
"client_id": "{{ga_client_id}}",
"events": [{
"name": "deal_won",
"params": {
"value": {{deal_value}},
"currency": "USD"
}
}]
}
Google Ads Offline Import
From CRM to Google Ads
Requirements:
- GCLID captured at lead time
- Conversion action created in Google Ads
- Stored in CRM with conversion data
Automated Import
# CSV format for Google Ads offline import
Parameters:TimeZone=America/Los_Angeles
Google Click ID,Conversion Name,Conversion Time,Conversion Value,Conversion Currency
abc123def456,SQL,2024-01-15 14:30:00,0,USD
xyz789ghi012,Closed Won,2024-01-20 09:15:00,50000,USD
Upload Methods
| Method | Best For | Frequency | |--------|----------|-----------| | Manual upload | Low volume | Weekly | | Scheduled upload | Medium volume | Daily | | API integration | High volume | Real-time |
Attribution Strategies
Multi-Touch Attribution
Store all touchpoints for accurate attribution:
// Store all touchpoints
function recordTouchpoint() {
const touchpoint = {
source: getUTMParam('utm_source') || document.referrer,
medium: getUTMParam('utm_medium') || 'organic',
campaign: getUTMParam('utm_campaign'),
timestamp: new Date().toISOString()
};
// Get existing touchpoints
let touchpoints = JSON.parse(localStorage.getItem('touchpoints') || '[]');
touchpoints.push(touchpoint);
// Keep last 10 touchpoints
localStorage.setItem('touchpoints', JSON.stringify(touchpoints.slice(-10)));
}
// Send all touchpoints with form submission
function getAttributionData() {
const touchpoints = JSON.parse(localStorage.getItem('touchpoints') || '[]');
return {
first_touch: touchpoints[0],
last_touch: touchpoints[touchpoints.length - 1],
all_touchpoints: touchpoints
};
}
CRM Custom Fields for Attribution
| Field | Description | |-------|-------------| | first_touch_source | Original source | | first_touch_date | First visit date | | last_touch_source | Converting source | | touchpoint_count | Number of visits | | days_to_convert | Time from first visit |
Reporting & Analytics
Closed-Loop Reports
| Metric | Calculation | |--------|-------------| | Cost per SQL | Ad spend / SQLs | | Cost per Opportunity | Ad spend / Opportunities | | Cost per Customer | Ad spend / Customers | | Revenue per Source | Total revenue by first_touch_source |
BigQuery Analysis
-- Revenue by original traffic source
SELECT
crm.first_touch_source,
crm.first_touch_medium,
COUNT(DISTINCT crm.contact_id) as customers,
SUM(crm.lifetime_revenue) as total_revenue,
AVG(crm.lifetime_revenue) as avg_ltv
FROM `project.crm_data.contacts` crm
WHERE crm.status = 'Customer'
GROUP BY first_touch_source, first_touch_medium
ORDER BY total_revenue DESC