Peak
Agency.
A performance marketing agency was spending every Monday morning pulling numbers from four platforms into a spreadsheet before client reports could start. Five analyst hours, every week, across 23 accounts. We automated the entire pipeline — data is ready at 6am before anyone opens their laptop.
- Client
- Peak Agency
- Year
- 2025
- Type
- API Integration
- Stack
- GA4 API · Meta Marketing API · Google Ads API · Stripe · PostgreSQL
- Timeline
- 6 weeks
Five Hours Every Monday.
Before the Work Could Start.
Peak manages paid media for 23 clients across Meta, Google, and Shopify. Every Monday, a senior analyst pulled the previous week's numbers from Meta Ads Manager, Google Ads, GA4, and Stripe — copying figures into a master spreadsheet, cross-referencing for discrepancies, and formatting per-client summaries before the team could begin writing recommendations.
The process took four to six hours depending on how cooperative Meta's dashboard was that morning. Client reports that should go out Monday afternoon routinely slipped to Wednesday. The analyst doing the pull was their most experienced media buyer — they were spending a quarter of their week on data entry.
4–6 hrs of manual data pull
Senior analyst occupied every Monday morning — unavailable for strategy, client calls, or campaign optimisations.
Reports slipping to Wednesday
Monday data prep pushed report writing to Tuesday, delivery to Wednesday — two days late, every week.
No true ROAS figure
GA4 revenue is modelled. Meta ROAS is self-reported. Neither matched Stripe's actual charge data — but no one was connecting them.
Copy-paste errors
Transposing numbers across four platforms into one spreadsheet, 23 times, weekly — errors were caught after clients had seen them.
Every platform Peak used had a production-grade API. The data pull was entirely manual not because automation was hard — but because no one had connected the four APIs to a shared schema. The spreadsheet was doing the job of a database. Replacing the spreadsheet with PostgreSQL meant the dashboard wrote itself.
Four Principles
That Shaped the Build.
Stripe as the ground truth for revenue
GA4 revenue is attribution-modelled. Meta and Google report revenue based on their own attribution windows, which overlap and double-count. Stripe records actual money that moved. All ROAS figures in the dashboard are calculated against Stripe — not the platform's self-reported numbers.
→ On first run, Stripe-anchored ROAS was 18–40% lower than platform-reported ROAS across accounts. Clients had been making budget decisions on inflated numbers.Daily snapshots over live API queries
Live querying all four APIs for 23 accounts on every dashboard load would hit rate limits immediately and produce inconsistent data across a single page view. Nightly snapshots run once, hit the APIs once, and serve consistent data all day from PostgreSQL.
→ Google Ads API has a strict daily quota that would be exhausted by 8am on live queries. Snapshots use the quota once per day at 3am.Custom dashboard over Looker Studio
Looker Studio was the obvious choice and the first thing we prototyped. It couldn't express the attribution bridge — joining GA4 sessions to Stripe charges via UTM parameters requires SQL that Looker Studio's data source model can't produce. A custom Next.js dashboard was the only way to get the true ROAS figure.
→ The prototype took two days in Looker Studio. The limitation was found on day two. Building custom took three weeks but produced numbers Looker Studio structurally cannot.Benchmark against the client's own baseline
Industry benchmarks are context-free. A 2.5× ROAS is excellent for one category and alarming for another. Each client's dashboard compares the current week against their own 90-day rolling average — giving account managers meaningful context without needing to remember industry norms.
→ Account managers reported this single feature reduced their Monday morning cognitive load more than anything else in the dashboard.Inside the
Pipeline.
GA4 — sessions and conversions
The GA4 Data API is queried nightly via the runReport method, pulling session data, conversion events, and revenue by channel for all 23 client properties. GA4's sampling kicks in above certain thresholds — the query is split into date ranges that stay below the sampling threshold to ensure accuracy. Results are written to a normalised sessions table in PostgreSQL.
Meta — campaign spend and ROAS
The Meta Marketing API is queried at the campaign, ad set, and ad level for all linked client accounts. Meta enforces strict rate limits per ad account — requests are batched and distributed across a 3-hour window to avoid throttling. Spend, impressions, clicks, and Meta-reported ROAS are written to the campaigns table alongside the account ID and date.
Google Ads — keyword and campaign performance
Google Ads performance data is pulled via the Google Ads Query Language (GAQL) API. Keyword-level data, quality scores, campaign spend, and conversion values are fetched per client MCC. The Google Ads API has a strict daily quota — a token bucket approach distributes requests across the day to ensure all 23 accounts complete within quota.
Stripe — actual revenue
Stripe is the ground truth for revenue. For clients with Stripe connected, the Events API pulls successful charge events, refunds, and subscription revenue for the period. Stripe revenue is the anchor for all ROAS calculations — overriding GA4's modelled revenue figures, which are subject to attribution and sampling errors.
Attribution bridge
Connecting ad spend to Stripe revenue requires UTM parsing. The pipeline joins GA4 session data (which carries UTM parameters) to Stripe charges using a sessionisation model and a configurable attribution window. The result is a true ROAS figure per channel, per campaign — numbers that GA4 and Meta can't produce independently.
Dashboard and delivery
A Next.js dashboard pulls from PostgreSQL at 6am daily, presenting each client's data in a consistent format: spend by channel, ROAS by campaign, week-over-week trends, and benchmark comparisons against the client's own 90-day baseline. A PDF export function generates client-ready reports — formatted and branded — in one click.
Numbers Worth
Shipping For.
Calls Worth
Explaining.
over Looker Studio
over a BI tool data model
over Vercel Cron alone