Connect Google Ads with HubSpot

Implementation Guide

Overview: Connecting Google Ads and HubSpot

The Google Ads-to-HubSpot integration solves one of the most persistent attribution gaps in B2B revenue operations: connecting paid acquisition spend to actual closed revenue in your CRM. Without this integration, marketing teams operate with a fundamental blind spot — they know a campaign generated 200 leads and $15,000 in spend, but cannot determine which of those leads became customers, what revenue they generated, or what the true cost-per-acquisition was at the campaign or ad group level.

HubSpot's native Google Ads integration (available in Marketing Hub Starter and above) addresses part of this gap by importing conversion data and enabling audience syncs. However, native integrations frequently lack the granularity that RevOps teams require: they don't capture gclid (Google Click ID) at the contact level, they don't push closed-won deal values back to Google as offline conversions, and they don't allow custom field mapping for advanced attribution models.

A properly architected integration therefore operates in two directions. The inbound direction pulls lead and conversion data from Google Ads into HubSpot, stamping each contact with campaign, ad group, keyword, and gclid metadata. The outbound direction pushes HubSpot CRM events — specifically deal stage progressions and closed-won events — back to Google Ads as offline conversions, closing the attribution loop and enabling Google's Smart Bidding algorithms to optimise toward actual revenue rather than form submissions.

Core Prerequisites

For Google Ads API access, you require a Google Ads Manager Account (MCC) with API access enabled. Navigate to Tools & Settings > Setup > API Center within your MCC to apply for access. You must provide a valid developer token; note that new developer tokens start at "Test Account" access level, which only allows API calls against test accounts. To access production data, you must submit the token for Standard Access review, which requires demonstrating a functioning OAuth implementation. The required OAuth 2.0 scopes are https://www.googleapis.com/auth/adwords. You must also obtain a customer-id for each Google Ads account you intend to query — this is the 10-digit number visible in the top right of the Google Ads UI (formatted as XXX-XXX-XXXX; strip the hyphens when passing it to the API as the login-customer-id header).

For HubSpot, you require either a Private App token (recommended) or a legacy API key. Private Apps are created under Settings > Integrations > Private Apps. The required scopes depend on your use cases: crm.objects.contacts.write and crm.objects.contacts.read for contact operations, crm.objects.deals.write for deal updates, and marketing-email.marketing_emails.execute if you intend to trigger workflow enrollments programmatically. For offline conversion imports back to Google Ads, no additional HubSpot API scope is needed — you are reading from HubSpot and writing to Google Ads.

For the HubSpot native Google Ads integration specifically, you must be a Super Admin in HubSpot and have Manager-level access (not Read-only) to the Google Ads account you are linking.

Top Enterprise Use Cases

The primary enterprise use case is closed-loop revenue attribution. By capturing the gclid parameter from landing page URLs into a HubSpot hidden form field, then mapping that field to a custom contact property, and finally importing that contact's deal-closed event back to Google Ads as an offline conversion, you enable Google's bidding engine to see the full revenue impact of each click. This typically yields a 15-30% improvement in ROAS for accounts spending over $50k/month, as Smart Bidding shifts budget toward keywords and audiences that generate revenue, not just form fills.

The second use case is lead quality segmentation. Not all Google Ads leads have the same conversion potential. By syncing HubSpot lifecycle stage and lead score back to Google Ads as Customer Match audiences, you can suppress ads from showing to lead profiles that historically churn, and increase bids for profiles that match your best-fit customer cohort. This requires the Audience Sync feature in HubSpot Marketing Hub Professional or above.

A third use case is campaign-level cost-per-acquisition reporting within HubSpot. By pulling Google Ads spend data via the API and storing it against UTM parameters at the campaign level, then joining it with HubSpot deal data, RevOps teams can build true CAC reports without relying on Salesforce or a separate BI tool.

Step-by-Step Implementation Guide

The most critical and often incorrectly implemented component of this integration is gclid capture. The Google Click ID is appended to your landing page URL by Google's auto-tagging feature as a query parameter: ?gclid=Cj0KCQjw.... You must capture this value on page load and pass it to HubSpot via a hidden form field.

To implement gclid capture, first create a custom HubSpot contact property named gclid (internal name: gclid, field type: Single-line text). Then add this JavaScript snippet to all landing pages served from Google Ads traffic — place it before the HubSpot forms embed code:

(function() {
  function getParam(name) {
    var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
    return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
  }
  var gclid = getParam('gclid');
  if (gclid) {
    localStorage.setItem('gclid', gclid);
  }
  var storedGclid = localStorage.getItem('gclid');
  if (storedGclid) {
    var field = document.querySelector('input[name="gclid"]');
    if (field) field.value = storedGclid;
  }
})();

Storing the gclid in localStorage ensures it persists across page navigations, covering multi-step forms and users who visit the landing page, navigate elsewhere on your site, and convert later. Add a hidden HubSpot form field mapped to the gclid property to every conversion form.

For the offline conversion import, you will query HubSpot's Deals API for deals that have moved to "Closed Won" within the last 24 hours. The API call is: GET https://api.hubapi.com/crm/v3/objects/deals?properties=dealname,amount,closedate,associated_contact_gclid&filterGroups=[{"filters":[{"propertyName":"dealstage","operator":"EQ","value":"closedwon"},{"propertyName":"closedate","operator":"GT","value":"{{yesterday_timestamp}}"}]}].

You must then associate this deal with its contact to retrieve the stored gclid. Use the associations API: GET https://api.hubapi.com/crm/v4/objects/deals/{dealId}/associations/contacts.

Once you have the gclid and deal amount, construct the Google Ads offline conversion upload payload using the Google Ads API's ConversionUploadService. The payload structure for a single conversion is:

{
  "conversions": [
    {
      "gclid": "Cj0KCQjw...",
      "conversion_action": "customers/1234567890/conversionActions/987654321",
      "conversion_date_time": "2025-06-15 14:32:00+00:00",
      "conversion_value": 4500.00,
      "currency_code": "USD"
    }
  ],
  "partial_failure": true
}

Setting partial_failure: true is essential — without it, a single invalid conversion in a batch causes the entire batch to fail. With it, valid conversions are processed and invalid ones return error details in partial_failure_error without blocking the batch.

Schedule this job to run nightly via a cron job or a scheduled Make scenario. Build in a 24-hour delay between deal close and conversion upload — Google recommends this buffer to account for click-to-conversion attribution windows and to ensure the gclid has not yet expired (gclids are valid for 90 days).

Common Pitfalls & Troubleshooting

A 403 Forbidden from the Google Ads API when querying production accounts with a developer token that has only Test Account access is one of the most common blockers for teams setting up this integration for the first time. The API returns a clear error code DEVELOPER_TOKEN_NOT_APPROVED in the response body. The resolution is to apply for Standard Access through the API Center — this process typically takes 1-2 business days.

A 400 Bad Request during offline conversion upload with error INVALID_ORIGINAL_CONVERSION_DATE_TIME means the conversion timestamp predates the click timestamp by more than is permitted. Ensure your deal close date stored in HubSpot is accurate and has not been backdated incorrectly.

HubSpot's API returns 429 Too Many Requests when you exceed 110 requests per 10 seconds for Private App tokens. For bulk contact operations during initial data migration, implement a token bucket rate limiter and batch your requests using HubSpot's Batch API endpoints (/crm/v3/objects/contacts/batch/create), which process up to 100 records per call.

The most insidious silent failure in this integration is gclid not being captured due to browser ITP (Intelligent Tracking Prevention) policies in Safari and Firefox, which restrict localStorage for cross-site tracking contexts. For high-Safari traffic segments, supplement localStorage with a first-party cookie set server-side via your landing page backend, which is exempt from ITP restrictions.