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.
LEAD GEN AGENCY // META + GOHIGHLEVEL → BIGQUERY → LOOKER
LEFT SIDE — the ad numbers, straight from Meta.
RIGHT SIDE — the same leads, verified in the CRM.
BOTTOM — the full funnel from first click to closed deal.
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.
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.
Spend, impressions, clicks, creative + adset detail. Pulled on schedule, never screenshotted.
Leads, stages, calls, deals. The source of truth for what a “lead” actually is.
Scheduled cloud functions land both sources daily at 07:00 GST. Failures alert instantly.
RAW → STAGING → MARTS. SQL models join ad → lead → deal, dedupe, and define one CPL for everyone.
Six decision-ready pages: overview, campaigns, creatives, lead quality, CRM pipeline, alerts.
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.
Three more pages from the dashboard, built in HTML with sample data. The demo further down lets you try it yourself.
PAID ADS × CRM // CPL VERIFIED AGAINST GOHIGHLEVEL
| CAMPAIGN | SPEND | LEADS · PIXEL | LEADS · CRM | CPL · CRM | ST |
|---|---|---|---|---|---|
| LEADFORM — BROAD META · ACTIVE 30D | $4,820 | 241 | 214 | $22.5 | |
| RETARGET — 30 DAY META · ACTIVE 30D | $2,140 | 108 | 96 | $22.3 | |
| LOOKALIKE 2% — US META · ACTIVE 30D | $3,610 | 171 | 142 | $25.4 | |
| UGC TEST — HOOK A META · ACTIVE 30D | $1,480 | 66 | 58 | $25.5 | |
| BRAND SEARCH META · ACTIVE 30D | $1,205 | 61 | 58 | $20.8 | |
| BROAD — NO FORM Q META · ACTIVE 30D | $953 | 74 | 34 | $28.0 |
PAID ADS × CRM // GOHIGHLEVEL STAGE TRUTH
| REP | LEADS | MEDIAN RESPONSE | CONTACTED | BOOKED | WON | NOTE |
|---|---|---|---|---|---|---|
| A. RAHMAN | 214 | 3M 40S | 81% | 58 | 19 | ★ FASTEST |
| S. KHAN | 198 | 4M 05S | 79% | 49 | 15 | |
| M. PATEL | 118 | 6M 55S | 74% | 27 | 8 | |
| UNASSIGNED | 72 | 2H+ | 41% | 14 | 4 | ⚠ FIX ROUTING |
AUTOMATION // BIGQUERY RULES → SLACK + AI → INBOX
CRM CPL hit $31.40 vs $25 target for 3 days. Rule suggests: shift 20% budget to LEADFORM — BROAD.
Leadform volume up. Speed-to-lead holding at 4m 12s. No action needed.
Meta + GoHighLevel → BigQuery completed 07:00. 14,208 rows. 0 errors.
$212 spent, 0 CRM leads in 72h. Auto-flagged for pause review.
Leadform — Broad carried the week: 214 CRM-verified leads at $22.5, comfortably under the $25 target. Retarget stayed the cheapest path to booked calls.
Lookalike 2% is the watch-item: CPL drifted above target while lead quality (MQL rate) fell to 54%. The volume is real, the intent is not.
Speed-to-lead held at 4m 12s — keep the unassigned routing fix on the list; those 72 leads convert at half the team rate.
A working copy of the dashboard with sample data. Change the date range, switch pages, hover over the chart, and sort the table.
LAST 30 DAYS · REFRESHED 07:00 GST
| STATUS |
|---|
DROPS: 78% → 32% → 71% → 44% · SPEED-TO-LEAD 4M 12S
TRY: SWITCH THE RANGE ✦ HOVER THE CHART ✦ SORT BY CPL ✦ ALL MOCK DATA, ALL LIVE HTML
A simplified version of the main query: ad spend on one side, CRM leads on the other, one cost-per-lead out.
-- 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)One cost-per-lead number, verified in the CRM. Everyone works from the same figure.
Reporting went from a weekly manual job to an automatic daily refresh with alerts.
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
If your ads and your CRM show different numbers, this same setup can be built for your business.