---
title: "Meta Ads Performance Analytics Pipeline"
description: "Unified paid and organic Meta Graph API analytics for a D2C handcraft brand"
canonical: https://erpstack.io/case-studies/meta-ads-analytics
markdown_url: https://erpstack.io/case-studies/meta-ads-analytics.md
publisher: ERPStack
---

# Meta Ads Performance Analytics Pipeline

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.

**Client:** Reference Build
**Sector:** retail
**Date:** 2026-06-02

This Analytics Pipeline is 1 of 3 published engagements for the same artisan D2C retail brand. The other 2 document the brand’s custom ERP development work: the Artisan Manufacturing ERP covers production and artisan payouts, and the Order-to-Cash & GST Compliance Engine covers invoicing and receivables. That ERP is a Next.js and React application in TypeScript on MongoDB Atlas, deployed to Vercel with shadcn/ui and Tailwind CSS, hosted alongside a Microsoft Azure-hosted manufacturing workload — and it is where the 5,313-row product catalog this pipeline reconciles against actually lives.

## 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.

## 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

## Technical performance telemetry

| Measure | Value |
| --- | --- |
| Insight Fields per Query | 8 |
| Breakdown Rows Normalized | 471 |
| Link-Click CTR | 2.16% |
| People reached (audit window) | 286K |
| Impressions normalized | 379,676 |
| Ad spend reconciled | ₹34,721 |
| Daily series unified | 31 days |

## Results

- **People reached (audit window):** 286K
- **Impressions normalized:** 379,676
- **Ad spend reconciled:** ₹34,721
- **Daily series unified:** 31 days

## Infrastructure stack

- Node.js
- Meta Graph API v19.0
- Insights API
- REST
- CSV Normalization
- JSON Data Feeds

## Engagement detail

# 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 from the Insights API into flat CSV matrices, and merges Facebook Page and Instagram organic reach into a single daily JSON feed the brand's custom ERP can read. Every number in this study is computed from the Node.js 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 Graph API surface paginates, rate-limits, and aggregates differently. For a small D2C retail 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:
1. **Cursor-Paginated Fetcher:** A generic `fetchAllPages` loop follows `paging.next` cursors until exhaustion, so every Node.js collector transparently handles the Graph API's page-size limits and returns complete result sets.
2. **Ads Insights Collector:** Queries the Insights API edge with eight metric fields per REST request — spend, impressions, reach, clicks, CPC, CPM, actions, and inline link clicks — with a configurable `time_range` and `time_increment` for daily granularity — 1 REST call per day of the window.
3. **Breakdown Collectors:** Separate Node.js collectors pull age/gender demographic splits (391 rows) and per-platform placement performance (80 rows), normalized to flat CSV for spreadsheet-free analysis.
4. **Organic Merger:** Facebook Page `page_impressions_unique` and Instagram `ig_reach` daily metrics from the Graph API 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 Graph API endpoint paginates; centralizing the `paging.next` walk 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 or a SaaS dashboard.
* **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 Graph API exports.

## What the Audit Window Showed
The normalized Node.js 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 inside the brand's custom ERP.

## Results & Retrospective
* Paid and organic Meta performance now share one daily axis; campaign reviews read from a single Node.js feed instead of three Graph API dashboards.
* Every aggregate is reproducible: re-running the Node.js collectors regenerates the same matrices from the Graph 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.

## Frequently asked questions

### Why did paid and organic Meta data need a pipeline at all?

Because Meta splits one brand’s performance across three products that never share an axis. Ads Manager reports paid spend, Page Insights reports organic Facebook reach, and Instagram insights report a third stream; each Graph API surface paginates, rate-limits and aggregates differently. Three disconnected reporting surfaces is technical debt in the reporting layer, and it made a simple question — did the campaign lift organic discovery — unanswerable. The Analytics Pipeline merges all three by calendar day into 1 Node.js feed.

### How does the Analytics Pipeline avoid truncating a result set?

By centralising pagination. Every Node.js collector in the Analytics Pipeline shares 1 fetchAllPages loop that follows the paging.next cursor until the REST endpoint stops returning one, so no collector can silently stop at the first page. The Node.js collectors are idempotent by design, and idempotency in API design is what makes the exported artifacts an audit trail: re-running a collector against Graph API v19.0 regenerates identical matrices — 391 demographic rows, 80 placement rows and 379 interest rows.

### What did the 31-day audit window actually show?

₹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, every figure recomputable from the Node.js CSV artifacts. 8,203 link clicks put the blended link-click CTR at 2.16%, with 104 add-to-cart events attributed downstream. The Analytics Pipeline’s Node.js collectors also exposed organic reach spikes invisible in weekly averages — 217 Instagram accounts reached on May 13 and 171 on May 17 — both aligned to campaign flight dates.

### Why request 8 metric fields in a single Insights query?

To keep each daily row internally consistent. Requesting spend, impressions, reach, clicks, CPC, CPM, actions and inline link clicks in 1 REST call against the Insights API means all 8 values describe the same attribution window, and it avoids N+1 round-trips against a rate-limited API. Splitting them across 8 calls would produce rows that quietly disagree, which is exactly the failure the Analytics Pipeline was built to remove.

### What happens to the unified feed next?

It is the ingestion contract for the brand’s dashboard prototype. The Analytics Pipeline ends at a normalised daily JSON series plus flat CSV matrices, so the visualisation phase reads the same 31-day Node.js output without touching collection code. Every aggregate stays reproducible from the Node.js collectors, and the same 5,313-row catalog export that reconciles ad-driven sessions against the live SKU set comes out of the brand’s own custom ERP rather than from Meta.

### How does the Analytics Pipeline connect to the brand’s ERP?

Through the catalog, not through a live integration. The Node.js collectors talk only to the Meta REST API; the 5,313-row product export they reconcile against is produced by the brand’s own custom ERP — a Next.js and React application in TypeScript on MongoDB Atlas, deployed to Vercel. Keeping the Analytics Pipeline read-only against Meta and file-based against the ERP means neither system can corrupt the other, and each side stays independently re-runnable.

### Why build this rather than read the numbers out of Ads Manager?

Because Ads Manager cannot join paid spend to organic reach, and a screenshot is not an audit trail. The Analytics Pipeline issues plain REST calls to the Graph API, writes flat CSV and JSON artifacts, and lets any aggregate be traced back to its source rows without a vendor API subscription. It is the same principle as the brand’s custom ERP development work: own the data extract, and every number in a review becomes reproducible.
