Google Analytics 4 + HubSpot Contact Lifecycle: Weekly Acquisition-Quality Digest to Microsoft Teams via Pipedream

Every Monday, this workflow cross-references last week's GA4 traffic sources with HubSpot contact creation and lifecycle progression — then posts a digest to Microsoft Teams showing which acquisition channels are actually producing contacts that convert.

The flow
Google Analytics logo
Source
Google Analytics
HubSpot logo
Process
HubSpot
Pipedream logo
Process
Pipedream
Microsoft Teams logo
Destination
Microsoft Teams

The stack in the order it runs — data flows from the source through to where it lands.

Why this stack

The most expensive early-stage growth mistake I've seen: optimizing for traffic channels that look great in GA4 but produce contacts that stall at 'Marketing Qualified Lead' in HubSpot forever. GA4 tells you volume and source. HubSpot tells you what happened to those people afterward. Without a data team or a BI tool, nobody joins these two views — so the gap just costs you money quietly.

Pipedream is the right call here because both GA4's Data API and HubSpot's CRM API require OAuth flows and multi-step data joins. That's easier to write in code than to wire up in a low-code drag-and-drop tool. Pipedream's component library has pre-built GA4 and HubSpot auth — you connect accounts in the UI and write the join logic in JavaScript steps.

GA4's Data API (`runReport`) returns sessions, users, and conversions broken down by `sessionDefaultChannelGroup` (Organic, Paid, Direct, Referral, etc.) for any date range you specify. HubSpot's Contacts API filtered by `createdate` and `lifecyclestage` gives you contacts created last week and their current stage. The join is by week — you're comparing each channel's share of traffic against the share of contacts it produced and how far those contacts have progressed.

One tradeoff to be honest about: the attribution join is approximate. GA4 and HubSpot use different attribution models, and not every GA4 session becomes an identified HubSpot contact. You need UTM parameters on all campaign links and HubSpot's tracking code installed correctly for the attribution to hold. If UTM coverage is below ~60% of inbound contacts, this digest will have attribution gaps — call that out explicitly in the Teams message so nobody over-indexes. And skip this playbook if you're spending more than $10K/month on paid acquisition. At that level you need a proper data warehouse (Supabase or BigQuery) and a BI layer. This is for teams spending $500–$5K/month who want directional signal without a data hire.

The stack (4)

  1. Google Analytics logo

    Web analytics most teams already run.

    The Data API makes traffic a free input for weekly ops digests.

  2. HubSpot logo

    CRM + marketing for go-to-market.

    Pipeline data becomes an automatable input for revenue reports.

  3. Pipedream logo

    Code-level workflows with hosted triggers.

    Drop into Node/Python mid-flow when no-code hits a wall.

  4. Microsoft Teams logo

    Chat + channels for Microsoft-365 shops.

    If the company is on Outlook/365, Teams is where reports get read — push there, not a separate tool.

How it runs

  1. 1

    Create the Pipedream workflow with a Monday morning CRON trigger

    In Pipedream, create a new workflow. Set the trigger to Schedule → CRON expression `0 8 * * 1` (8 AM UTC every Monday). Name it `ga4-hubspot-acquisition-quality`. Connect your Google Analytics account via Pipedream's Google OAuth integration and your HubSpot account via Pipedream's HubSpot app. Both connections are UI-driven — no manual token management.

  2. 2

    Query GA4 Data API for last week's sessions by channel

    Add a Node.js code step. Use the `@googleapis/analyticsdata` npm package, which is available in Pipedream. Call `runReport` with: `dateRanges = [{startDate: 'last Monday', endDate: 'last Sunday'}]`, `dimensions = [{name: 'sessionDefaultChannelGroup'}]`, `metrics = [{name: 'sessions'}, {name: 'newUsers'}, {name: 'conversions'}]`. Your GA4 Property ID is in the GA4 console under Admin → Property Settings. Parse the response into a map: `{channel: {sessions, newUsers, conversions}}`. Watch for the channel name 'Unassigned' — if it's >20% of traffic, flag it. That means UTM coverage is poor.

  3. 3

    Query HubSpot for contacts created last week, broken down by original source

    Add a second code step. Use HubSpot's POST `/crm/v3/objects/contacts/search` endpoint with filters: `createdate BETWEEN [last Monday, last Sunday]`. Return properties: `createdate`, `hs_analytics_source`, `lifecyclestage`, `hs_lead_status`. The `hs_analytics_source` property maps to GA4 channels (ORGANIC_SEARCH, PAID_SEARCH, DIRECT_TRAFFIC, etc.). Build a map: `{source: {count, mqls, sqls, customers}}` where mqls/sqls/customers are counts of contacts at each lifecycle stage. Paginate using HubSpot's `after` cursor — each page returns 100 contacts max.

  4. 4

    Join the two datasets and compute channel quality scores

    Add a Function step to normalize channel names between GA4 and HubSpot (GA4 uses 'Organic Search', HubSpot uses 'ORGANIC_SEARCH' — build a lookup map). For each channel, compute: `contactConversionRate = contacts / sessions * 100`, `mqlRate = mqls / contacts * 100`, `customerRate = customers / contacts * 100`. Rank channels by `customerRate` descending. Flag any channel where sessions > 200 but customerRate = 0 — that's wasted acquisition spend with a specific source to investigate.

  5. 5

    Build the weekly digest table in text format

    Construct the Teams message as a markdown table — Teams supports basic markdown in channel messages. Columns: Channel | Sessions | New Contacts | MQL Rate | Customer Rate | Flag. Sort rows by Customer Rate descending. Add a summary line at the top: `Top channel this week: {channel} at {customerRate}% customer conversion rate.` Add a warning line if any channel is flagged. Cap the table at 6 channels — beyond that nobody reads it.

  6. 6

    Post the digest to Microsoft Teams via Incoming Webhook

    Add an HTTP Request step in Pipedream. POST to your Teams channel's Incoming Webhook URL. Set Content-Type to `application/json`. Use Teams' MessageCard format (or Adaptive Card v1.3 for richer formatting). The table renders best in a code block inside Teams — use triple-backtick fencing for the channel table to preserve alignment. Test the webhook URL first with a simple curl command before wiring it into Pipedream.

  7. 7

    Add an anomaly alert for channel-level drops

    After the main digest post, add conditional logic: if any channel that had >100 sessions last week shows a week-over-week drop in `contactConversionRate` of >30% (compare against last week's data stored in a Google Sheets history row), post a second Teams message tagged `@channel` in `#growth-alerts`. Compute week-over-week by reading the prior row from Google Sheets and comparing. This turns the digest from a passive report into an active signal.

  8. 8

    Write the run's data to Google Sheets for historical trending

    Add a Google Sheets step at the end: Append Row. Write: week_of, top_channel, top_channel_customer_rate, total_contacts_created, total_sessions, overall_contact_conversion_rate, flagged_channels (comma-separated). After 8 weeks of data, open this sheet in Looker Studio and build a proper trend chart in 10 minutes. Pipedream generates the data; the Sheets tab is your free warehouse for it.

Want me to build this for you instead?

Product Audit and CTO Mode run out of this same thinking. If you’re reading this thinking “I want this, but in my product” — let’s talk.

See services

More like this