Skip to main content
Back to Blog
analytics

Cross-Platform Tracking: Unifying Meta, Google & TikTok Data in One Dashboard

RedClaw Performance Team
3/9/2026
14 min read

Cross-Platform Tracking: Unifying Meta, Google & TikTok Data in One Dashboard

Most performance marketers live in three or more ad platforms simultaneously. Google Ads says ROAS is 4.2x. Meta says it is 3.8x. TikTok says 2.1x. GA4 tells a completely different story. When every platform reports different numbers using different attribution models, making accurate budget allocation decisions feels impossible.

The solution is a unified cross-platform tracking system that pulls data from all ad platforms and analytics tools into one consistent view. This guide covers the technical architecture, API integrations, data normalization methods, and dashboard design needed to build a single source of truth for cross-channel performance marketing.

Why This Matters: Without unified tracking, budget allocation is based on each platform's self-reported (and naturally inflated) numbers. Cross-platform unification reveals the true ROAS of each channel, identifies over-credited and under-credited campaigns, and enables data-driven budget shifts that improve overall marketing efficiency by 15-30%.


Table of Contents

  1. Why Platform-Level Reporting Fails
  2. Cross-Platform Architecture
  3. Data Collection: APIs and Connectors
  4. Data Normalization
  5. Attribution Alignment
  6. Building the Unified Dashboard
  7. Automated Reporting and Alerts
  8. Implementation Roadmap
  9. FAQ

Why Platform-Level Reporting Fails

The Self-Attribution Problem

Every ad platform takes credit for conversions that occurred within its attribution window. When a user is exposed to ads on multiple platforms, each one claims the conversion. The result: the sum of platform-reported conversions exceeds your actual total conversions.

Actual orders this month: 1,000

Platform-reported conversions:
├── Google Ads: 650 conversions
├── [Meta Ads](https://www.facebook.com/policies/ads/): 520 conversions
├── [TikTok Ads](https://ads.tiktok.com/help/article/tiktok-advertising-policies-ad-creatives-landing-page): 180 conversions
└── Total claimed: 1,350 (35% inflation)

This is not a tracking error -- it is a fundamental feature of platform attribution. Each platform legitimately contributed to some of those conversions, but they cannot agree on how much credit each deserves.

Different Reporting Standards

Each platform uses different terminology, metrics, and calculations:

MetricGoogle AdsMeta AdsTikTok Ads
ImpressionsStandardStandardStandard
ClicksClicks (all)Link clicks or All clicksClicks
CTRClicks / ImpressionsLink CTR or CTR (all)CTR
ConversionsUser-configurableResults (configurable)Conversions
RevenueConv. valuePurchase valueValue
ROASConv. value / CostPurchase ROASROAS
AttributionDDA (default)7d click / 1d view7d click / 1d view
Reporting timeClick timeImpression timeClick time
CurrencyAccount currencyAccount currencyAccount currency

For a deeper understanding of why platform data differs, see our Tracking Data Discrepancies guide.


Cross-Platform Architecture

The Three-Layer Model

Layer 1: Data Collection
├── Google Ads API → Raw campaign data
├── Meta Marketing API → Raw ad data
├── TikTok Marketing API → Raw campaign data
├── GA4 API → Analytics data (neutral observer)
└── Backend/CRM → Ground truth (orders, revenue)

Layer 2: Data Processing
├── Normalize metrics (clicks, CTR, conversions)
├── Align attribution windows
├── Currency conversion
├── De-duplicate cross-platform conversions
└── Calculate blended metrics

Layer 3: Data Presentation
├── Unified dashboard (Looker Studio, Tableau, custom)
├── Automated reports (weekly, monthly)
├── Alerts (performance anomalies, tracking issues)
└── Budget recommendation engine

Technology Stack Options

ComponentFree OptionPaid OptionEnterprise
Data extractionCustom scripts (Python)Funnel.io, SupermetricsFivetran, Adverity
Data warehouseGoogle Sheets, BigQuery (free tier)BigQuery, SnowflakeSnowflake, Redshift
TransformationSQL views, Pythondbt, Dataformdbt Cloud
DashboardLooker StudioTableau, Power BILooker, Sisense
AlertingGoogle Apps ScriptCustom webhooksPagerDuty, Datadog

Data Collection: APIs and Connectors

Google Ads API

# Python: Pull Google Ads campaign data
from google.ads.googleads.client import GoogleAdsClient

client = GoogleAdsClient.load_from_storage("google-ads.yaml")
ga_service = client.get_service("GoogleAdsService")

query = """
  SELECT
    campaign.name,
    campaign.id,
    metrics.impressions,
    metrics.clicks,
    metrics.cost_micros,
    metrics.conversions,
    metrics.conversions_value,
    segments.date
  FROM campaign
  WHERE segments.date DURING LAST_30_DAYS
  ORDER BY metrics.cost_micros DESC
"""

response = ga_service.search_stream(
  customer_id="1234567890",
  query=query
)

for batch in response:
  for row in batch.results:
    campaign_data = {
      'platform': 'google_ads',
      'campaign_name': row.campaign.name,
      'campaign_id': str(row.campaign.id),
      'date': str(row.segments.date),
      'impressions': row.metrics.impressions,
      'clicks': row.metrics.clicks,
      'cost': row.metrics.cost_micros / 1_000_000,  # Convert micros
      'conversions': row.metrics.conversions,
      'revenue': row.metrics.conversions_value
    }
    save_to_warehouse(campaign_data)

Meta Marketing API

# Python: Pull Meta Ads campaign data
import requests

access_token = "YOUR_ACCESS_TOKEN"
ad_account_id = "act_1234567890"

url = f"https://graph.facebook.com/v19.0/{ad_account_id}/insights"
params = {
  'access_token': access_token,
  'fields': 'campaign_name,campaign_id,impressions,clicks,spend,'
            'actions,action_values',
  'date_preset': 'last_30d',
  'level': 'campaign',
  'time_increment': 1,  # Daily breakdown
  'limit': 500
}

response = requests.get(url, params=params)
data = response.json()

for row in data.get('data', []):
  # Extract purchase conversions and revenue from actions array
  conversions = 0
  revenue = 0
  for action in row.get('actions', []):
    if action['action_type'] == 'purchase':
      conversions = int(action['value'])
  for action_value in row.get('action_values', []):
    if action_value['action_type'] == 'purchase':
      revenue = float(action_value['value'])

  campaign_data = {
    'platform': 'meta_ads',
    'campaign_name': row['campaign_name'],
    'campaign_id': row['campaign_id'],
    'date': row['date_start'],
    'impressions': int(row['impressions']),
    'clicks': int(row['clicks']),  # Note: This is "all clicks"
    'cost': float(row['spend']),
    'conversions': conversions,
    'revenue': revenue
  }
  save_to_warehouse(campaign_data)

TikTok Marketing API

# Python: Pull TikTok Ads campaign data
import requests
import json

access_token = "YOUR_ACCESS_TOKEN"
advertiser_id = "1234567890"

url = "https://business-api.tiktok.com/open_api/v1.3/report/integrated/get/"
headers = {
  'Access-Token': access_token,
  'Content-Type': 'application/json'
}

payload = {
  'advertiser_id': advertiser_id,
  'report_type': 'BASIC',
  'dimensions': ['campaign_id', 'stat_time_day'],
  'data_level': 'AUCTION_CAMPAIGN',
  'metrics': ['campaign_name', 'impressions', 'clicks', 'spend',
              'conversion', 'total_complete_payment_value'],
  'start_date': '2026-02-09',
  'end_date': '2026-03-09',
  'page_size': 1000
}

response = requests.get(url, headers=headers, params={'filtering': json.dumps(payload)})
# Process response similar to above

GA4 Data API

# Python: Pull GA4 data as neutral cross-channel view
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import RunReportRequest, DateRange, Dimension, Metric

client = BetaAnalyticsDataClient()

request = RunReportRequest(
  property='properties/XXXXXXXXX',
  date_ranges=[DateRange(start_date='30daysAgo', end_date='today')],
  dimensions=[
    Dimension(name='sessionSource'),
    Dimension(name='sessionMedium'),
    Dimension(name='sessionCampaignName'),
    Dimension(name='date')
  ],
  metrics=[
    Metric(name='sessions'),
    Metric(name='conversions'),
    Metric(name='purchaseRevenue'),
    Metric(name='totalUsers')
  ]
)

response = client.run_report(request)

For comprehensive GA4 API configuration, see our GA4 Setup Complete Guide.


Data Normalization

Metric Alignment

Different platforms define the same metrics differently. You must normalize before comparing.

# Data normalization layer
def normalize_campaign_data(raw_data):
  platform = raw_data['platform']

  normalized = {
    'platform': platform,
    'campaign_name': raw_data['campaign_name'],
    'campaign_id': raw_data['campaign_id'],
    'date': raw_data['date'],
    'impressions': raw_data['impressions'],
    'cost': raw_data['cost'],
    'cost_currency': 'USD',  # After currency conversion
  }

  # Normalize clicks
  if platform == 'meta_ads':
    # Meta "clicks" includes all clicks (profile, comments, etc.)
    # Use "link_clicks" for comparable metric (if available)
    normalized['clicks'] = raw_data.get('link_clicks', raw_data['clicks'])
  else:
    normalized['clicks'] = raw_data['clicks']

  # Normalize conversions
  normalized['conversions'] = raw_data['conversions']
  normalized['revenue'] = raw_data['revenue']

  # Calculate derived metrics
  normalized['ctr'] = (normalized['clicks'] / normalized['impressions'] * 100
                       if normalized['impressions'] > 0 else 0)
  normalized['cpc'] = (normalized['cost'] / normalized['clicks']
                       if normalized['clicks'] > 0 else 0)
  normalized['cpa'] = (normalized['cost'] / normalized['conversions']
                       if normalized['conversions'] > 0 else 0)
  normalized['roas'] = (normalized['revenue'] / normalized['cost']
                        if normalized['cost'] > 0 else 0)

  return normalized

Currency Normalization

If you advertise in multiple currencies, convert all costs and revenue to a single base currency:

# Currency conversion using daily exchange rates
from datetime import date

EXCHANGE_RATES = {
  '2026-03-09': {
    'TWD': 0.031,   # TWD to USD
    'EUR': 1.09,    # EUR to USD
    'GBP': 1.27     # GBP to USD
  }
}

def convert_to_usd(amount, currency, date_str):
  if currency == 'USD':
    return amount
  rate = EXCHANGE_RATES.get(date_str, {}).get(currency, 1)
  return amount * rate

Naming Convention Alignment

Campaign names often differ slightly across platforms. Create a mapping table:

# Campaign name normalization
CAMPAIGN_MAPPING = {
  # Google Ads name → Unified name
  'Brand - Exact Match - US': 'brand_search_us',
  'Brand - Broad Match - US': 'brand_search_us',
  # Meta name → Unified name
  'Brand Awareness - US - Broad': 'prospecting_awareness_us',
  '[ABO] Prospecting - Interests - US': 'prospecting_interest_us',
  # TikTok name → Unified name
  'Prospecting_US_25-44_Interest': 'prospecting_interest_us',
}

def normalize_campaign_name(platform_name):
  return CAMPAIGN_MAPPING.get(platform_name, platform_name)

Attribution Alignment

The Attribution Challenge Across Platforms

Each platform uses its own attribution model and window, making direct comparison meaningless without alignment.

Alignment Strategies

Strategy 1: Use GA4 as the neutral referee

GA4 sees all traffic regardless of source and applies a single attribution model. Use GA4-attributed conversions for cross-channel comparison:

-- BigQuery: GA4 data as cross-channel attribution source
SELECT
  session_source AS source,
  session_medium AS medium,
  session_campaign AS campaign,
  COUNT(DISTINCT user_pseudo_id) AS users,
  COUNTIF(event_name = 'purchase') AS conversions,
  SUM(CASE WHEN event_name = 'purchase'
    THEN ecommerce.purchase_revenue ELSE 0 END) AS revenue
FROM `project.analytics_XXXXXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260209' AND '20260309'
GROUP BY 1, 2, 3
ORDER BY revenue DESC

Strategy 2: Standardize attribution windows

Compare all platforms using the same window. Set Meta to 7-day click only (no view-through) and Google Ads to 7-day click for apples-to-apples comparison.

Strategy 3: Blended ROAS with backend truth

Use total revenue from your backend and total cost from all platforms:

Blended ROAS = Total Backend Revenue / Total Ad Spend (all platforms)

Channel ROAS (blended): Keep each platform's cost but use GA4 attributed revenue
  Google Ads ROAS = GA4 Google Revenue / Google Ads Spend
  Meta ROAS = GA4 Meta Revenue / Meta Spend
  TikTok ROAS = GA4 TikTok Revenue / TikTok Spend

For understanding how attribution models affect these calculations, refer to our Conversion Tracking Complete Guide.


Building the Unified Dashboard

Dashboard Structure

Design your dashboard around the decisions it needs to support:

Dashboard: Cross-Platform Performance
├── Executive Summary (Page 1)
│   ├── Blended ROAS (big number)
│   ├── Total Spend vs Total Revenue (trend chart)
│   ├── Cost per Acquisition by platform (bar chart)
│   └── Budget split vs Revenue split (pie charts)
│
├── Platform Comparison (Page 2)
│   ├── Side-by-side: Spend, Conversions, ROAS per platform
│   ├── Platform ROAS trend (line chart, 30 days)
│   ├── CPA trend by platform (line chart)
│   └── Discrepancy analysis (platform-reported vs GA4)
│
├── Campaign Drill-Down (Page 3)
│   ├── All campaigns, all platforms, sortable table
│   ├── Columns: Platform, Campaign, Spend, Clicks, Conv, Revenue, ROAS, CPA
│   ├── Filter by platform, date range, campaign type
│   └── Color coding: Green = above target ROAS, Red = below
│
├── Funnel Analysis (Page 4)
│   ├── Cross-platform funnel: Impression → Click → Visit → Convert
│   ├── Drop-off rates by platform
│   ├── Landing page performance by traffic source
│   └── Device breakdown
│
└── Budget Recommendations (Page 5)
    ├── Current budget allocation vs optimal (based on marginal ROAS)
    ├── Recommended shifts with expected impact
    └── Historical performance of budget changes

Looker Studio Implementation

-- SQL view for Looker Studio dashboard (BigQuery)
CREATE VIEW `project.marketing.unified_daily` AS

-- Google Ads data
SELECT
  'Google Ads' AS platform,
  campaign_name,
  date,
  impressions,
  clicks,
  cost,
  conversions,
  revenue,
  SAFE_DIVIDE(revenue, cost) AS roas,
  SAFE_DIVIDE(cost, conversions) AS cpa,
  SAFE_DIVIDE(clicks, impressions) * 100 AS ctr
FROM `project.marketing.google_ads_daily`

UNION ALL

-- Meta Ads data
SELECT
  'Meta Ads' AS platform,
  campaign_name,
  date,
  impressions,
  link_clicks AS clicks,
  spend AS cost,
  purchase_conversions AS conversions,
  purchase_revenue AS revenue,
  SAFE_DIVIDE(purchase_revenue, spend) AS roas,
  SAFE_DIVIDE(spend, purchase_conversions) AS cpa,
  SAFE_DIVIDE(link_clicks, impressions) * 100 AS ctr
FROM `project.marketing.meta_ads_daily`

UNION ALL

-- TikTok Ads data
SELECT
  'TikTok Ads' AS platform,
  campaign_name,
  date,
  impressions,
  clicks,
  cost,
  conversions,
  revenue,
  SAFE_DIVIDE(revenue, cost) AS roas,
  SAFE_DIVIDE(cost, conversions) AS cpa,
  SAFE_DIVIDE(clicks, impressions) * 100 AS ctr
FROM `project.marketing.tiktok_ads_daily`;

For proper UTM tracking that feeds into cross-platform analysis, see our UTM Parameters Usage Guide.


Automated Reporting and Alerts

Weekly Performance Report

Automate a weekly email report summarizing cross-platform performance:

# Weekly report template
def generate_weekly_report(data):
  report = {
    'period': 'Last 7 days',
    'summary': {
      'total_spend': sum(d['cost'] for d in data),
      'total_revenue': sum(d['revenue'] for d in data),
      'blended_roas': sum(d['revenue'] for d in data) / sum(d['cost'] for d in data),
      'total_conversions': sum(d['conversions'] for d in data)
    },
    'by_platform': {},
    'top_campaigns': [],
    'alerts': []
  }

  # Platform breakdown
  for platform in ['Google Ads', 'Meta Ads', 'TikTok Ads']:
    platform_data = [d for d in data if d['platform'] == platform]
    if platform_data:
      report['by_platform'][platform] = {
        'spend': sum(d['cost'] for d in platform_data),
        'revenue': sum(d['revenue'] for d in platform_data),
        'roas': (sum(d['revenue'] for d in platform_data) /
                 sum(d['cost'] for d in platform_data)),
        'conversions': sum(d['conversions'] for d in platform_data)
      }

  # Performance alerts
  for platform, metrics in report['by_platform'].items():
    if metrics['roas'] < 2.0:
      report['alerts'].append(f"WARNING: {platform} ROAS below 2.0 ({metrics['roas']:.1f}x)")

  return report

Real-Time Anomaly Detection

# Alert when a platform's daily spend or conversions deviate significantly
def check_anomalies(today_data, historical_avg):
  alerts = []

  for platform in today_data:
    spend_deviation = abs(today_data[platform]['cost'] - historical_avg[platform]['cost']) / historical_avg[platform]['cost']
    conv_deviation = abs(today_data[platform]['conversions'] - historical_avg[platform]['conversions']) / max(historical_avg[platform]['conversions'], 1)

    if spend_deviation > 0.5:
      alerts.append({
        'severity': 'HIGH',
        'platform': platform,
        'metric': 'spend',
        'message': f"{platform} spend deviated {spend_deviation:.0%} from average"
      })

    if conv_deviation > 0.5:
      alerts.append({
        'severity': 'CRITICAL',
        'platform': platform,
        'metric': 'conversions',
        'message': f"{platform} conversions deviated {conv_deviation:.0%} from average"
      })

  return alerts

For deeper analysis of the data flowing through this system, see our Ad Data Analysis for Beginners guide.


Implementation Roadmap

Phase 1: Data Collection (Weeks 1-2)

  1. Set up API access for Google Ads, Meta, TikTok
  2. Build data extraction scripts (or configure connector tool)
  3. Create data warehouse tables/views
  4. Schedule daily data pulls

Phase 2: Normalization (Week 3)

  1. Implement metric normalization logic
  2. Create campaign name mapping
  3. Set up currency conversion
  4. Validate normalized data against platform dashboards

Phase 3: Dashboard (Weeks 4-5)

  1. Build the unified dashboard in Looker Studio or your tool of choice
  2. Create the five-page structure described above
  3. Add interactive filters (date range, platform, campaign)
  4. Test with stakeholders

Phase 4: Automation (Week 6)

  1. Set up automated weekly reports
  2. Configure anomaly detection alerts
  3. Build budget recommendation logic
  4. Document maintenance procedures

Ongoing Maintenance

  • Review API changes quarterly (platforms update their APIs frequently)
  • Update campaign mapping when new campaigns launch
  • Refine anomaly detection thresholds based on false positive rate
  • Review attribution alignment settings as platform defaults change

For the full tracking setup that feeds into cross-platform analysis, see our Pixel + CAPI Dual Tracking Setup guide.


Building a cross-platform dashboard from scratch is complex. RedClaw builds unified tracking dashboards for performance marketers, handling the API integrations, data normalization, and ongoing maintenance so you can focus on optimization. Get a free tracking audit


FAQ

What is the easiest way to start cross-platform reporting without building a data warehouse?

Start with Google Looker Studio (free) plus a data connector like Supermetrics or Funnel.io ($50-200/month). These connectors pull data from Google Ads, Meta, and TikTok directly into Looker Studio without requiring a data warehouse, APIs, or coding. The trade-off is less flexibility in data normalization and attribution alignment, but it gets you started with side-by-side platform comparison within hours.

How do I handle attribution inflation when summing conversions across platforms?

Never sum platform-reported conversions as your total. Instead, use your backend (CRM, order management system) as the source of truth for total conversions. Then use GA4's cross-channel attribution to understand each platform's contribution. For budget allocation, calculate each platform's blended contribution rate: GA4-attributed conversions per platform divided by total backend conversions.

Should I include organic channels in my cross-platform dashboard?

Yes. Including organic search (from GA4 + Search Console), email (from your ESP), and direct traffic provides the full picture of your marketing performance. Paid media does not operate in isolation; organic channels often play supporting roles in conversion paths. A dashboard showing only paid channels misses the interaction effects between paid and organic touchpoints.

How often should I refresh cross-platform data?

Daily refreshes are sufficient for most businesses. Ad platform APIs often have a 24-48 hour delay for finalized data (conversions may be adjusted retroactively due to attribution windows and modeling). Running hourly refreshes creates noise from preliminary data. If you need real-time monitoring, use each platform's native dashboard for intraday checks and your unified dashboard for strategic decisions using settled data.

Can I compare ROAS across platforms if they use different attribution models?

Not directly -- comparing Google Ads DDA ROAS to Meta 7d-click-1d-view ROAS is misleading. To make valid comparisons, either standardize attribution windows (set all platforms to 7-day click only) or use GA4 as the neutral attribution layer. GA4 applies one consistent model across all channels, giving you comparable ROAS figures. Alternatively, use blended ROAS (backend revenue divided by platform cost) as the universal comparison metric.


Stop making budget decisions from platform silos. Our cross-platform dashboards give you one number for every metric, across every channel. Check your ROAS


Related reading: Pixel + CAPI Dual Tracking Setup | GA4 Setup Complete Guide | Conversion Tracking Complete Guide | UTM Parameters Usage Guide | Ad Data Analysis for Beginners


Explore our tracking & analytics services →

Share:

Maximize Your Ad Budget ROI

From account setup to full-funnel tracking, we handle it all.

  • Dedicated account manager with real-time optimization
  • Full tracking infrastructure — every dollar accounted for
  • Cross-platform expertise: Meta, Google, TikTok

📬 Subscribe to Our Newsletter

Weekly insights on ad strategies, industry trends, and practical tips. No fluff.

We never share your email. Unsubscribe anytime.