Sync qualified HubSpot leads to a Google Ads audience daily

Every morning, refresh a Google Ads Customer Match list from HubSpot so paid campaigns retarget the right leads and stop wasting spend on the wrong ones.

Agentic Task
HubSpotGoogle AdsSlack BotMarketingSalesData SyncDaily Digests
PromptCreate

Every weekday at 7:00am in my local timezone, sync my qualified HubSpot pipeline into a Google Ads Customer Match audience so my paid campaigns can retarget the right people and exclude the wrong ones. Post a short recap in Slack when it finishes.

Configuration I want to set once and reuse each run: (1) the HubSpot qualification rule, either a list of lifecycle stages (default: marketingqualifiedlead, salesqualifiedlead, opportunity) or a lead score threshold on the hubspotscore property; (2) the name of the Google Ads Customer Match user list to keep in sync (default: 'HubSpot Qualified Leads'); (3) the Slack channel for the recap (default: #growth).

Step 1 — Pull qualified contacts from HubSpot. Use HubSpot Search Contacts with a filter matching my configured qualification rule (lifecycle stage IN the configured stages, or hubspotscore >= the configured threshold). Request the email, firstname, lastname, and country properties, and paginate through all results. Only keep contacts that have a valid email.

Step 2 — Find or create the Google Ads Customer Match audience. Call Google Ads List Customer (User) Lists and look for a user list with the configured name. If none exists, call Create Customer Match User List to create one of type CRM_BASED with upload_key_type CONTACT_INFO and remember the resource name. Otherwise reuse the existing list's resource name.

Step 3 — Compute the diff. Read the current membership of the Customer Match list (via GAQL if needed) and compare it against the freshly pulled qualified set. Determine which SHA-256 hashed emails need to be added (new qualified leads not yet in the list) and which need to be removed (previously synced contacts that are no longer qualified). Normalize every email before hashing: lowercase, trim whitespace.

Step 4 — Reconcile the audience. Call Google Ads Add or Remove Members From Customer Match List once with the additions and once with the removals (or a single combined offline user data job — whichever the operation supports). Pass hashed_email values (lowercase hex SHA-256). Do NOT send raw emails. Skip the job entirely if both add and remove sets are empty.

Step 5 — Post the recap. Send a Slack Bot message to the configured channel summarizing the run: audience name, counts added, counts removed, total members in the list after the update, and the HubSpot rule used (e.g. 'lifecycle stage in MQL, SQL, Opportunity'). Keep the message short and skimmable, with a one-line headline and a bulleted breakdown. If the run was a no-op (nothing to add or remove), still post a brief 'no changes today' line so the team knows it ran.

Error handling: if Google Ads is not yet approved for Standard developer token access, if the Customer Match list is not eligible for uploads, or if HubSpot rate-limits me, post a Slack message describing the failure instead of silently failing. Do not partially update the audience — abort and report if anything mid-flow errors.

Additional information

What does this prompt do?
  • Pulls your current qualified pipeline from HubSpot every morning, filtered by lifecycle stage (MQL, SQL, Opportunity) or a lead score threshold.
  • Adds new qualified contacts to a Google Ads Customer Match audience and removes anyone who no longer meets the bar, so the list always reflects reality.
  • Handles the security bits for you (emails are hashed before they leave HubSpot) and creates the audience on first run if it does not exist yet.
  • Posts a short Slack recap each morning with the audience name and how many people were added or removed.
What do I need to use this?
  • A HubSpot account with contacts tagged by lifecycle stage or lead score.
  • A Google Ads account where you can build a Customer Match audience.
  • A Slack workspace and a channel for the daily recap.
How can I customize it?
  • Change which HubSpot lifecycle stages or lead score threshold defines a qualified lead.
  • Rename the Customer Match audience or point the workflow at an existing one you already run campaigns against.
  • Pick the Slack channel and the time of day the sync should run.

Frequently asked questions

Do my leads' email addresses get sent to Google in plain text?
No. Emails are converted into a one-way SHA-256 hash before they leave HubSpot, which is exactly what Google Ads Customer Match expects. Google can match hashed emails against signed-in Google accounts without ever seeing the raw address.
What if the Google Ads audience does not exist yet?
On the very first run, the workflow creates a new Customer Match audience for you and starts filling it. On every run after that, it just updates the existing list.
Can I use this to exclude closed-won customers from prospecting campaigns?
Yes. You can point this at any HubSpot segment, including closed-won customers, and use the resulting audience as an exclusion list in your Google Ads campaigns so you stop paying to reach people who already bought.
How often does the audience refresh?
By default it runs once every morning, but you can pick any schedule. If someone becomes an MQL on Tuesday, they will be in the audience by Wednesday morning.
What happens if a contact is no longer qualified?
They are removed from the Customer Match audience on the next run, so you stop retargeting people who cooled off or converted.

Stop retargeting the wrong leads on Google.

Connect HubSpot, Google Ads, and Slack once, and Geni keeps your Customer Match audience in sync with your qualified pipeline every single morning.