Meta says it drove the conversion. Google says it drove the conversion. Both are telling the truth — from their own perspective. Neither can see the other's data.
This is the multi-platform attribution problem. Every ad platform has an incentive to take credit and a technical inability to share context. The only place all the data can coexist is your warehouse.
Why every platform over-reports
Each ad platform tracks conversions using its own pixel or tag. When a user clicks a Meta ad, visits your site, later clicks a Google ad, and then converts — both platforms claim the conversion:
- Meta saw a click → conversion within its attribution window. Credit: Meta.
- Google saw a click → conversion within its attribution window. Credit: Google.
Your actual conversions: 1. Platform-reported conversions: 2.
This isn't a bug. It's how attribution windows work. Meta defaults to a 7-day click / 1-day view window. Google defaults to 30-day click. If both touched the user within their windows, both count it.
At scale, this means your total "platform-reported conversions" across Meta + Google + LinkedIn + TikTok can be 40-60% higher than your actual conversion count. Every budget decision based on those inflated numbers is wrong.
What a unified paid-media model looks like
The architecture:
Google Ads ──→ BigQuery (via Fivetran)
Meta Ads ──→ BigQuery (via Fivetran)
LinkedIn Ads ──→ BigQuery (via Fivetran)
GA4 ──→ BigQuery (native export)
CRM ──→ BigQuery (via Fivetran)
│
▼
dbt models
│
▼
Sigma / Looker
Staging: normalize across platforms
Each platform has its own schema, naming conventions, and metric definitions. The first job is making them speak the same language:
-- models/staging/stg_ads__unified_spend.sql
SELECT
'google_ads' AS platform,
campaign_name,
report_date,
impressions,
clicks,
spend,
platform_conversions,
platform_conversion_value
FROM {{ ref('stg_google_ads__campaign_performance') }}
UNION ALL
SELECT
'meta_ads' AS platform,
campaign_name,
report_date,
impressions,
clicks,
spend,
platform_conversions,
platform_conversion_value
FROM {{ ref('stg_meta_ads__campaign_performance') }}
UNION ALL
SELECT
'linkedin_ads' AS platform,
campaign_name,
report_date,
impressions,
clicks,
spend,
platform_conversions,
platform_conversion_value
FROM {{ ref('stg_linkedin_ads__campaign_performance') }}Campaign taxonomy
This is where most teams lose control. Campaign names across platforms follow no standard. Google has "Brand - US - Search - Exact," Meta has "Prospecting_Lookalike_US_Q2," LinkedIn has something else entirely.
We solve this with a dbt seed file:
-- seeds/campaign_taxonomy.csv
platform,campaign_name,channel,audience,region,objective
google_ads,Brand - US - Search - Exact,search,brand,us,awareness
google_ads,Nonbrand - US - Search,search,nonbrand,us,acquisition
meta_ads,Prospecting_Lookalike_US_Q2,social,prospecting,us,acquisition
meta_ads,Retargeting_US_AllVisitors,social,retargeting,us,conversion
linkedin_ads,Decision Makers - Data Leaders,social,abm,us,acquisition-- models/intermediate/int_ads__enriched.sql
SELECT
s.*,
t.channel,
t.audience,
t.region,
t.objective
FROM {{ ref('stg_ads__unified_spend') }} s
LEFT JOIN {{ ref('campaign_taxonomy') }} t
ON s.platform = t.platform
AND s.campaign_name = t.campaign_nameNow you can answer "what's our total spend on acquisition campaigns across all platforms?" — a question no single platform UI can answer.
The truth model: warehouse conversions
-- models/marts/mart_paid_media_performance.sql
SELECT
ads.platform,
ads.channel,
ads.audience,
ads.region,
SUM(ads.spend) AS total_spend,
SUM(ads.clicks) AS total_clicks,
SUM(ads.platform_conversions) AS platform_reported_conversions,
COUNT(DISTINCT c.contact_id) AS warehouse_leads,
COUNT(DISTINCT CASE WHEN d.stage = 'closed_won' THEN d.deal_id END) AS warehouse_deals,
SUM(CASE WHEN d.stage = 'closed_won' THEN d.amount END) AS warehouse_revenue,
SAFE_DIVIDE(
SUM(CASE WHEN d.stage = 'closed_won' THEN d.amount END),
SUM(ads.spend)
) AS true_roas
FROM {{ ref('int_ads__enriched') }} ads
LEFT JOIN {{ ref('int_ga4__sessions') }} ga
ON ads.campaign_name = ga.utm_campaign
AND ads.report_date = DATE(ga.session_start)
LEFT JOIN {{ ref('stg_hubspot__contacts') }} c
ON ga.user_pseudo_id = c.ga_user_id
LEFT JOIN {{ ref('stg_hubspot__deals') }} d
ON c.contact_id = d.contact_id
GROUP BY 1, 2, 3, 4The platform_reported_conversions column shows what each platform claims. The warehouse_deals column shows what actually closed. The gap between them is the over-reporting.
The honest state of multi-touch attribution
Let's be direct: perfect multi-touch attribution is a myth. You cannot determine with certainty how much credit each touchpoint deserves in a multi-channel journey. The user saw a LinkedIn ad, googled your brand name, read a blog post, got retargeted on Meta, and then converted. How much credit does each get?
There are models:
- Last-click — all credit to the final touchpoint. Simple, wrong, but at least consistent.
- First-click — all credit to the first touchpoint. Favors awareness channels.
- Linear — equal credit to all touchpoints. Fair but uninformative.
- Time-decay — more credit to touchpoints closer to conversion. Better, but the decay curve is arbitrary.
- Data-driven — statistical modeling based on your actual conversion paths. Google offers this in GA4 and Ads.
Our recommendation: don't chase the perfect model. Start with last-click for efficiency decisions (which channel closes?) and first-click for investment decisions (which channel opens?). Report both. Let the business decide which lens matters for which question.
The warehouse makes this possible because it has all the touchpoints. No single platform does.
What this looks like in practice
We built this for a marketing agency managing campaigns across Google, Meta, and 3 social platforms for a portfolio of clients. Before the warehouse:
- Each platform was reported separately in its own dashboard
- "Total conversions" was the sum of platform-reported conversions (over-counted by ~45%)
- Budget allocation was based on which platform showed the best ROAS (which was whichever platform had the most generous attribution window)
After:
- Single dashboard with unified spend, actual conversions, and true ROAS per platform
- Campaign taxonomy that works across all platforms
- Budget reallocation based on warehouse-verified performance, not platform self-reporting
The meta-insight: Meta's reported ROAS was higher than Google's. The warehouse showed Google's true ROAS was higher. The agency had been over-investing in Meta for months based on Meta's own numbers.
The takeaway
Every ad platform is grading its own homework. The numbers aren't wrong — they're incomplete. Each platform sees its own touchpoints and attributes accordingly. None of them can see the full journey.
Your warehouse is the only place where all the touchpoints, all the spend, and all the actual revenue coexist. It's the only place where "which channel performs best?" gets an honest answer.
We build unified paid-media models that connect Google, Meta, LinkedIn, and your CRM in one warehouse — so budget decisions are based on real numbers, not platform self-reporting. Book a discovery call and we'll show you what your true ROAS looks like.