Retail / Marketing Analytics
Meta Ads Performance Analytics Pipeline
Unified paid and organic Meta Graph API analytics for a D2C handcraft brand
Quick Summary / TL;DR
an artisan D2C brand ran Meta campaigns with paid and organic data fragmented across Ads Manager, Page Insights, and Instagram. ERPStack built a cursor-paginated Node.js pipeline against Graph API v19.0 that reconciled ₹34,721 of spend, 285,994 people reached, and 379,676 impressions into one 31-day daily feed with demographic and placement breakdowns.
- 286K
- People reached (audit window)
- 379,676
- Impressions normalized
- ₹34,721
- Ad spend reconciled
- 31 days
- Daily series unified
Thirty-one days, finally on one axis
This is the pipeline's actual output feed: Instagram reach and Facebook Page unique impressions merged by calendar day. The May 13 flight start is visible in the data — which is the entire argument for unifying the axes.
The audited paid funnel
Totals across 18 ad rows in the 31-day window — every figure recomputable from the pipeline's CSV artifacts.
Bar widths √-scaled for legibility — labels are the exact audited values.
- Spend reconciled
- ₹34,721
- Spend-weighted CPM
- ₹91.45
- Blended link-click CTR
- 2.16%
Where the impressions and the rupees actually went
Impression share by placement
- Instagram71.8%
- Facebook27.3%
- Other placements0.9%
Lifetime placement export · 80 rows normalized
Ad spend by age band (₹)
- 18–246,230
- 25–3414,517
- 35–4412,627
- 45–5411,319
- 55–646,049
- 65+3,340
Lifetime demographic export · 391 rows normalized
One pagination loop, every collector
Meta paginates everything. Centralizing the cursor walk means no collector can silently truncate a result set — 391 demographic rows, 80 placement rows, and 379 interest rows all arrive complete.
GET /insights
8 metric fields, one attribution window
append(data)
rows accumulate into the result set
paging.next?
follow the cursor — or the set is complete
- spend
- impressions
- reach
- clicks
- cpc
- cpm
- actions
- inline_link_clicks
The challenge
an artisan D2C brand was running Meta ad campaigns with performance data fragmented across three surfaces: Ads Manager for paid spend, Page Insights for organic reach, and Instagram insights for a second organic channel. Nobody could line up a day of ad spend against that day's organic reach, and campaign decisions were made from screenshots. The brand needed one daily series unifying paid and organic signals, reconciled against its 5,313-row product catalog.
The architecture
We built a Node.js ingestion pipeline against the Meta Graph API (v19.0): cursor-paginated collectors walk the Insights API for ads performance (eight metric fields per query), age/gender demographics, and per-placement breakdowns, then merge Facebook Page and Instagram daily reach into a single normalized JSON feed. The pipeline audited ₹34,721 of ad spend across 18 ad rows — 285,994 people reached, 379,676 impressions, 8,203 link clicks — into one queryable 31-day series powering a dashboard prototype.
The 5,313-row product catalog export closes the loop between ad sessions and the live SKU set.
Case Study: Meta Ads Performance Analytics Pipeline
Executive Summary
This case study covers a marketing-analytics engineering engagement for an artisan D2C retail brand. The deliverable is a Node.js data pipeline that walks the Meta Graph API (v19.0) with cursor-based pagination, normalizes paid campaign insights, audience demographics, and placement breakdowns into flat CSV matrices, and merges Facebook Page and Instagram organic reach into a single daily JSON feed. Every number in this study is computed from the pipeline's own output artifacts.
The Business Challenge
Meta splits a brand's performance picture across products: Ads Manager reports paid spend and conversions, Page Insights reports organic Facebook reach, and Instagram insights report a third stream. Each surface paginates, rate-limits, and aggregates differently. For a small D2C team, the practical consequence was that paid and organic performance were never on the same axis — a spend spike on May 13 could not be compared against that day's organic reach without manual export gymnastics. Attribution questions ("did the campaign lift organic discovery?") were unanswerable.
System Architecture
The pipeline is a set of small, single-purpose Node.js collectors sharing one pagination engine:
- Cursor-Paginated Fetcher: A generic
fetchAllPagesloop followspaging.nextcursors until exhaustion, so every collector transparently handles Meta's page-size limits and returns complete result sets. - Ads Insights Collector: Queries the
/insightsedge with eight metric fields per request — spend, impressions, reach, clicks, CPC, CPM, actions, and inline link clicks — with a configurabletime_rangeandtime_incrementfor daily granularity. - Breakdown Collectors: Separate collectors pull age/gender demographic splits (391 rows) and per-platform placement performance (80 rows), normalized to flat CSV for spreadsheet-free analysis.
- Organic Merger: Facebook Page
page_impressions_uniqueand Instagramig_reachdaily metrics merge by date into one JSON series — 31 consecutive days in the audit window.
[Graph API v19.0] ──(cursor pagination)──> [Collectors: ads / demographics / placements]
│ │
[Page + IG daily metrics] ──(merge by date)──> [Unified daily JSON feed] ──> [Dashboard prototype]
Key Engineering Decisions
- Cursor pagination as a shared primitive: Every Meta endpoint paginates; centralizing the
paging.nextwalk in one function means no collector can silently truncate a result set. - Eight fields, one query: Requesting spend, impressions, reach, clicks, CPC, CPM, actions, and inline link clicks in a single Insights call avoids N+1 API round-trips and keeps each daily row internally consistent — all fields describe the same attribution window.
- Flat CSV as the normalization target: Demographics and placement breakdowns land as flat matrices (471 rows total), so the brand can audit any aggregate back to its source rows without custom tooling.
- Merge-by-date, not merge-by-source: The unified feed is keyed on the calendar day, which is the axis the business thinks in. Paid spend, Page reach, and IG reach become columns of one row instead of three exports.
What the Audit Window Showed
The normalized series made the brand's real performance legible for the first time:
- ₹34,721 of spend across 18 ad rows reached 285,994 unique people and served 379,676 impressions — a spend-weighted CPM of ₹91.45.
- 8,203 link clicks put the blended link-click CTR at 2.16%, with 104 add-to-cart events attributed downstream.
- The organic series exposed reach spikes (217 IG accounts reached on May 13, 171 on May 17) that were invisible in weekly averages — both aligned with campaign flight dates.
- The 5,313-row product catalog export reconciles ad-driven sessions against the storefront's live SKU set.
Results & Retrospective
- Paid and organic Meta performance now share one daily axis; campaign reviews read from a single feed instead of three dashboards.
- Every aggregate is reproducible: re-running the collectors regenerates the same matrices from the API, and the CSV artifacts are the audit trail.
- The unified feed is the ingestion contract for the brand's dashboard prototype — the next phase visualizes the same JSON without touching collection code.
Engineered capabilities
- Cursor-based pagination engine that walks paging.next until every result set is complete
- Eight-field Insights queries: spend, impressions, reach, clicks, CPC, CPM, actions, inline link clicks
- Age/gender demographic breakdown normalized to 391 flat CSV rows
- Per-placement platform breakdown normalized to 80 rows
- Facebook Page and Instagram daily reach merged by calendar date into one series
- 5,313-row product catalog export reconciled against ad-driven sessions