End-to-End Analytics for Google Ads + HubSpot
Business Case
A performance marketer needs to understand which keywords and search queries in Google Ads generate actual sales, not just clicks and leads. A standard Google Ads report shows conversions based on the ad account's own data, but doesn't show how many of those conversions turned into paid deals in HubSpot. This calls for end-to-end analytics with ROMI calculated for each keyword.
Connections
This report requires two connections:
Google Ads — the
google-adsconnector- Permissions:
google_ads_read - Status:
active
- Permissions:
HubSpot CRM — the
hubspotconnector- Permissions: read access to deals and contacts
- Status:
active
Matching scheme (matching preset):
- Matching Preset: Google Ads + HubSpot (GCLID) — a built-in LightLead preset
- Matching key: GCLID (Google Click ID) — a unique click identifier passed via UTM tags or Google Ads auto-tagging
- Mapping:
google_ads.gclid→hubspot.contact.gclid→hubspot.deal.associated_contact.gclid
You need to set up a hidden "GCLID" field at the contact level in HubSpot. When a form on your website is submitted, the GCLID from the URL must be saved into this field. Without this, the matching won't work — check this setting before running the report.
Report Settings
- Name:
End-to-End Analytics Google Ads → HubSpot - Report type: attribution report with GCLID matching
- Matching Preset: Google Ads + HubSpot (GCLID)
- Data sources: all
google-adsandhubspotconnections - Date range:
last_30_days - Attribution window: 30 days (click → lead) + 90 days (lead → deal)
- Grouping: by campaign (
campaign_name), ad group (ad_group_name), keyword (keyword_name), pipeline (pipeline_name) - Sorting: by ROMI descending
- Timezone:
Europe/Moscow
Parameters
{
"date_range": "last_30_days",
"matching_preset": "google-ads-hubspot-gclid",
"attribution_window": {
"click_to_lead_days": 30,
"lead_to_deal_days": 90
},
"group_by": ["campaign_name", "ad_group_name", "keyword_name", "pipeline_name"],
"sort": { "field": "romi", "direction": "desc" },
"timezone": "Europe/Moscow",
"limit": 2000
}The lead_to_deal_days: 90 attribution window suits B2B companies with a long sales cycle. For B2C, reduce it to 30. Grouping by keyword (keyword_name) is the main value of this setup, since it lets you make decisions at the search-query level, not just the campaign level.
Metrics
| Metric | Type | Source | Description |
|---|---|---|---|
cost | currency | Google Ads | Budget spent |
impressions | number | Google Ads | Impressions |
clicks | number | Google Ads | Clicks |
ctr | percent | Google Ads | Click-through rate |
cpc | currency | Google Ads | Average cost per click |
leads | number | HubSpot | Number of leads (contacts with GCLID) |
mql | number | HubSpot | Marketing Qualified Leads |
sql | number | HubSpot | Sales Qualified Leads |
deals_created | number | HubSpot | Deals created |
deals_won | number | HubSpot | Deals won |
revenue | currency | HubSpot | Revenue from won deals |
Calculated metrics:
{
"calculated_metrics": [
{
"name": "cpl",
"formula": "IF([leads] > 0, [cost] / [leads], 0)",
"type": "money",
"description": "Cost per lead"
},
{
"name": "cpl_sql",
"formula": "IF([sql] > 0, [cost] / [sql], 0)",
"type": "money",
"description": "Cost per qualified lead"
},
{
"name": "romi",
"formula": "IF([cost] > 0, ([revenue] - [cost]) / [cost] * 100, 0)",
"type": "percent",
"description": "ROMI: return on ad investment"
},
{
"name": "lead_to_mql_cr",
"formula": "IF([leads] > 0, ROUND([mql] / [leads] * 100, 1), 0)",
"type": "percent",
"description": "Lead → MQL conversion rate"
},
{
"name": "revenue_per_keyword",
"formula": "IF([deals_won] > 0, [revenue] / [deals_won], 0)",
"type": "money",
"description": "Average revenue per deal by keyword"
}
]
}ROMI > 0% means the ad spend is paying off. ROMI > 100% means every ruble spent on ads brings back a ruble of profit. ROMI by keyword lets you find "golden" queries with ROMI > 500% and raise bids on them.
Filters
{
"logic": "AND",
"conditions": [
{
"field": "cost",
"operator": "GREATER_THAN",
"value": 300
},
{
"field": "clicks",
"operator": "GREATER_THAN",
"value": 20
},
{
"field": "impressions",
"operator": "GREATER_THAN",
"value": 500
}
]
}cost > 300— minimum budget for statistical significanceclicks > 20— minimum clicks for a keywordimpressions > 500— sufficient reach
To analyze a specific keyword match type: match_type EQUAL "exact" or match_type EQUAL "phrase".
Write Mode
Mode: Replace
Data from two sources and the GCLID matching require a full rewrite for consistency. In addition, the 90-day attribution window means that data from the last 3 months can change as new deals close.
Schedule
{
"schedule": {
"enabled": true,
"interval": "daily",
"time": "07:30",
"timezone": "Europe/Moscow",
"retry_on_failure": true,
"max_retries": 3
}
}Daily at 7:30. Keep in mind that ROMI for the last 1-2 weeks will look understated (deals haven't closed yet). For decision-making, rely on data over last_90_days or at least last_30_days, ignoring the most recent week.
Expected Result
- Status:
completedorpartial - Row count: campaigns × groups × keywords (typically 100-800 rows depending on the size of the keyword set)
- End-to-end metrics: ROMI for each keyword — you can see which queries pay off and which run at a loss
- Lead qualification: CPL vs. CPL(SQL) — a query can produce cheap leads that fail to qualify, visible from a low MQL/SQL conversion rate
- Actions: queries with ROMI < 0% get excluded or have bids lowered; queries with ROMI > 300% get bids raised and are moved into a separate campaign
Screenshot
[Screenshot]
[Screenshot]