CASE FILE 01NDA — CLIENT GENERALISEDALL DATA MOCKED

Ad data and CRM data in one funnel.

A lead generation agency ran Meta ads and tracked leads in GoHighLevel. The two tools showed different numbers every week. This project connected both into one dashboard with one set of numbers.

CLIENTLEAD GEN AGENCY
MODELPAID SOCIAL → BOOKED CALLS
STACKMETA · GHL · BIGQUERY · LOOKER
ENGAGEMENT5 PHASES · FULL SYSTEM
STATUSLIVE · AUTOMATED
PAGE 01 / 06 — OVERVIEWAI-BUILT · LIVE HTML
EXHIBIT A
1

LEFT SIDE — the ad numbers, straight from Meta.

2

RIGHT SIDE — the same leads, verified in the CRM.

3

BOTTOM — the full funnel from first click to closed deal.

The Brief

Two tools, two different stories.

The agency was good at getting leads. The reporting was the problem. Meta counted one number of leads. The CRM counted another. Nobody knew which one was right.

Every week someone exported files and stitched them together by hand. The report was out of date before anyone read it. Budget decisions were based on guesswork.

It was a reporting problem, not a marketing problem.

THE GOAL: make Meta and the CRM show the same numbers, automatically, every morning.

✗ THE PROBLEMS

  • Meta and the CRM showed different costs per lead — sometimes 30% apart
  • Leads were counted twice, and sales weren’t linked back to ads
  • Weekly reports were built by hand from exports
  • Nobody knew which campaigns led to booked calls or sales
  • New leads sometimes sat for hours before anyone called them
The System

How the system works.

Ad data and CRM data are pulled into BigQuery every morning. SQL combines them into one set of numbers, shown in Looker Studio, with alerts and an AI summary on top.

Meta Ads API

Spend, impressions, clicks, creative + adset detail. Pulled on schedule, never screenshotted.

GoHighLevel API

Leads, stages, calls, deals. The source of truth for what a “lead” actually is.

Pipelines

Scheduled cloud functions land both sources daily at 07:00 GST. Failures alert instantly.

BigQuery

RAW → STAGING → MARTS. SQL models join ad → lead → deal, dedupe, and define one CPL for everyone.

Looker Studio

Six decision-ready pages: overview, campaigns, creatives, lead quality, CRM pipeline, alerts.

Action Layer

Slack alert rules + a Monday 07:00 AI summary that ends in suggested actions, not adjectives.

RAW SIGNAL IN → AUTOMATED ACTION OUT — THE SAME SIX-STEP SYSTEM FROM THE HOMEPAGE, DEPLOYED FOR REAL.

The Process

The process, step by step.

01

Audit

  • Traced the pixel-vs-CRM lead gap to its causes
  • Mapped every GoHighLevel stage in use (and misuse)
  • Flagged duplicate + unattributed conversions
02

Measurement Plan

  • UTM + naming convention the buyers actually follow
  • Stage definitions signed off by sales AND media
  • KPI tree with CPL defined off CRM leads only
03

Implementation

  • Scheduled API pipelines: Meta + GHL → BigQuery
  • SQL models joining ad → lead → deal
  • QA: totals reconciled against Ads Manager
04

Dashboard Build

  • Six Looker pages, one per decision
  • Pixel and CRM shown side by side — the gap is the insight
  • Filters wired to campaign, adset, source, rep
05

Automate & Optimise

  • 07:00 daily refresh, failure-alerted
  • Five Slack rules: CPL breach, pacing, zero-lead adsets…
  • Monday AI digest with suggested actions
The Build

The dashboard, page by page.

Three more pages from the dashboard, built in HTML with sample data. The demo further down lets you try it yourself.

PAGE 02 / 06 — CAMPAIGNSLIVE HTML · MOCK DATA
EXHIBIT B
Campaign Deep-Dive

Ad leads vs real leads.

  • Ad-reported leads and CRM-verified leads sit side by side. The gap between them is the point.
  • Cost per lead is tracked against a $25 target, using CRM numbers only.
  • Ads are ranked by their real cost per lead, not by clicks.
PAGE 03 / 06 — LEAD QUALITYLIVE HTML · MOCK DATA
EXHIBIT C
Lead Quality & CRM

Where leads drop off.

  • Each CRM stage shows how many leads make it to the next one.
  • The map shows each lead source by volume and quality, with a clear action for each corner.
  • Response times per rep showed that unassigned leads were the biggest leak.
PAGE 06 / 06 — ALERTS + AILIVE HTML · MOCK DATA
EXHIBIT D
Automation Layer

Alerts come to the team.

  • Rules watch the numbers and post to Slack when something breaks or costs rise.
  • Every Monday, an AI summary explains the week in plain language.
  • Every alert includes a suggested next step.
Live Demo

Try it yourself.

A working copy of the dashboard with sample data. Change the date range, switch pages, hover over the chart, and sort the table.

lookerstudio.google.com/paid-ads-crm — INTERACTIVE RECREATION

Paid Ads × CRM — Live

LAST 30 DAYS · REFRESHED 07:00 GST

MOCK DATA
AD SPEND
CLICKS
LEADS · CRM
CPL · CRM
BOOKED
WON

LEADS & SPEND — DAILY LEADSSPEND

CLICK → CLOSE FUNNEL

CLICKS
LEADS
BOOKED
SHOWED
WON
SPEND · LEADS PIXEL · LEADS CRM · GAP
STATUS

STAGE CONVERSION — CRM

NEW
CONTACTED
BOOKED
SHOWED
WON

DROPS: 78% → 32% → 71% → 44% · SPEED-TO-LEAD 4M 12S

SOURCE QUALITY — VOLUME × MQL% HOVER THE DOTS

SCALE ↗ TEST MORE FIX QUALITY KILL FB LEADFORM — high volume · 72% MQL IG REELS — mid volume · 60% MQL LOOKALIKE 2% — mid volume · 38% MQL RETARGET — low volume · 86% MQL BRAND SEARCH — low volume · 80% MQL BROAD NO-FORM-Q — high volume · 30% MQL WHATSAPP — low volume · 35% MQL

TRY: SWITCH THE RANGE HOVER THE CHART SORT BY CPL ALL MOCK DATA, ALL LIVE HTML

Under the Hood

A look at the SQL.

A simplified version of the main query: ad spend on one side, CRM leads on the other, one cost-per-lead out.

bigquery · marts/cpl_crm.sql
-- marry ad spend to CRM truth (simplified)
WITH spend AS (
  SELECT date, campaign_id, SUM(spend) AS spend
  FROM meta_ads.insights
  GROUP BY 1, 2
),
crm AS (
  SELECT l.campaign_id,
         COUNT(*) AS leads_crm,
         COUNTIF(d.stage = 'won') AS wins
  FROM ghl.leads l
  LEFT JOIN ghl.deals d USING (lead_id)
  GROUP BY 1
)
SELECT s.date, s.campaign_id, s.spend,
       c.leads_crm, c.wins,
       SAFE_DIVIDE(s.spend, c.leads_crm) AS cpl_crm
FROM spend s JOIN crm c USING (campaign_id)
Deliverables

What was delivered.

The Results

The results.

ONE CPL.

One cost-per-lead number, verified in the CRM. Everyone works from the same figure.

07:00 DAILY.

Reporting went from a weekly manual job to an automatic daily refresh with alerts.

CLICK → DEAL.

Every campaign can be traced from the first click to booked calls and closed deals.

NOTE: CLIENT DETAILS GENERALISED · ALL NUMBERS ARE SAMPLE DATA · DASHBOARDS ARE HTML RECREATIONS

Next Steps

Want this for your business?

If your ads and your CRM show different numbers, this same setup can be built for your business.