Case Study 09 · Marketing Operations

CEO Ad Spend Dashboard

A single screen that shows what a lead or a sale actually cost, not what Meta's Ads Manager reports. Built for a founder who only has two questions on a Monday morning, and could not get a clean answer to either from the ad platform itself.

Industry

EdTech / Marketing Operations

Build Time

One afternoon, using systems already in place

Platform Cost

$0, no new vendor, no subscription

Stack

Cloudflare Worker + KV · Meta Marketing API · Store API · Team Chat Bot

3x Real cost per lead, versus the platform figure
3x Real cost per sale, versus the platform figure
5 of 6 "Purchases" that were not clean ad wins
60s To read the true numbers, on a phone

Two questions. Ads Manager answers neither cleanly.

If you run Meta ads to generate leads or sales, only two questions matter on a Monday morning: what did a lead cost last week, and what did a sale cost. Ads Manager is built for media buyers, it has two hundred columns, and every column is correct in Meta's terms. But Meta's terms are not the business's terms.

Its "lead" is not the business's lead: it adds together on-Facebook form submissions and website pixel events, so the same parent can count twice. Its "purchase" is not a clean sale: Meta credits an ad with any purchase by anyone who saw or clicked it in the last week, including the ones a sales team phoned in and closed themselves. Its "results" are not one thing: a chat conversation, a form fill and a website signup all sit under the same word, so a blended cost per result tells you nothing. And the business's real numbers live elsewhere entirely, in a store, a CRM, a team chat, reconciling them by hand takes twenty minutes and rarely happens.

A campaign that looks like it returns 35 times its spend gets more budget. A lead that looks like it costs $12 does not get questioned. When both figures are roughly three times too good, every budget decision built on them is wrong, and nobody finds out until the quarter is over.

One business, one day, two very different stories.

A real one-day snapshot from a founder-led education business, rounded and anonymised. Same ads, same day. The left is what Meta reported. The right is what the business's own systems showed once asked the right question.

Holiday programs, purchase ads Meta reported What actually happened
Purchases 6 2, the other 4 were sales-team closes or unrelated orders
Revenue ~$3,960 ~$780 from ads
Cost per sale ~$18 ~$55, three times higher
Return on spend 35.7x ~7x, still excellent, not miraculous
Free trial class, instant-form ads Meta API total What actually happened
Form leads 21 8, the other 13 were website pixel events already counted elsewhere
Cost per lead ~$12 ~$32, matching the platform's own Results column exactly

Nothing about the ads was wrong. The reporting was. A CEO reading the platform's numbers would have scaled the holiday campaign and left the free class campaign alone. Reading the real numbers, both decisions deserve a second look.

Built from how it would be read, not from what data existed.

The dashboard started from a sticky note, not a data model: readable in sixty seconds on a phone, first thing in the morning, no sideways scrolling. One number per goal, each funnel gets a tab, each tab has one hero metric. Results counted where they happen, leads the way the sales team receives them, orders in the store, never in Meta's pixel. Sales-assisted results stripped out entirely, if a person sold it, the ad does not get the credit. Grouped by funnel, not by campaign, since campaign names change weekly and funnels do not. No new logins, no new tools, no subscription.

Funnel The CEO's question The hero metric
Messaging ads Are people starting conversations, and at what price? Cost per conversation started, plus conversation rate
Lead magnet What does a download cost? Cost per form lead
Free trial class What does a class booking cost? Cost per form lead, split by instant form and website
Holiday intensives What does an ads-driven sale cost, and what does it return? Cost per order and revenue, ads only

One small program, three data sources, no vendors.

The dashboard is a single small program running on Cloudflare Workers, the same platform the business already used for its other automations. No database of its own, no analytics product behind it. Each time it is opened it asks three systems for the same date range and does the arithmetic itself.

01

Meta Marketing API

Spend, impressions, link clicks and every action type per ad set per day, plus how each ad set is set up, read with a never-expiring system user token. The ad set's setup decides which funnel tab it belongs to.

02

Online store API

Completed orders in the date range, with products, totals and the customer's phone number. Orders containing a holiday program are the sales for that tab.

03

Team chat, sales channel

An automation already posted one line per sale naming the closer and the customer. Every phone number mentioned is matched against store orders. A match means the sales team sold it, so it is removed from the ads-only figure.

Six tiles across the top of every tab, a daily chart of spend against cost per result, and one table of ad sets sortable by any column. The best-performing quarter of ad sets carries a small coral dot. Paused ad sets are labelled. Ad sets with no spend are hidden. New test ad sets file themselves into the right tab from their name or their setup, so the CEO never has to decode a campaign name.

Ads performance dashboard showing funnel tabs (Messaging leads, Lead magnet, Holiday intensives, Free trial class), date range controls, six hero tiles (spend, messaging leads, cost per lead, convo started rate, purchases, revenue, link CTR) and a daily spend versus cost per lead chart

The Messaging leads tab: hero tiles, funnel switcher, and the daily spend versus cost per lead chart

Ad set table showing name, spend, CPM, link CTR, messaging leads, convo rate, CPL, purchases, cost per purchase, revenue and ROAS per ad set, with a totals row and a coral dot marking top-quarter performers

The ad set table: every column the CEO's decision actually depends on, sortable, with top performers flagged

Built to be boring. Results are cached so the ad platform is asked at most a few times an hour, the last good numbers stay on screen if it is unavailable, and the whole thing is one file that can be changed and redeployed in under a minute.

A custom build knows things a platform reader never can.

Every off-the-shelf reporting tool would have shown the flattering numbers with total confidence, because it reads the ad platform and nothing else. It does not know a sales team exists, that a chat conversation is the real lead, or that the store is the source of truth for a sale.

Leads, not merged

The platform merges on-Facebook form leads with website pixel leads into one number. Instant-form ad sets now count on-Facebook leads only, landing-page ad sets count website leads only, matching the platform's own Results column exactly.

Sales, not assisted

The platform credits ads with every purchase by anyone who saw or clicked them in the last week. The dashboard reads real orders from the store and removes any whose phone number appears in the sales team's own channel.

Funnels, not campaign names

Campaign names change weekly and mean nothing to a CEO. Every ad set sorts into a funnel tab by its name first, its setup second, so new tests file themselves without anyone touching the dashboard.

One hero metric per funnel

One blended cost per result tells a CEO nothing. Cost per conversation, cost per form lead, cost per ads-only order, each lives on its own tab, never mixed together.

Specific to one business. The pattern is not.

Decide the one question per funnel, count the answer where it actually happens, subtract what the ads did not earn, and keep it to a single screen. The same recipe applies to any business buying leads or sales on Meta.

The hero metric is a cost per something real

Not clicks, not reach, not "results". Something the business would actually pay for.

Results counted in the system that owns them

The store for sales, the booking tool for bookings, the CRM for qualified enquiries. The ad platform's opinion becomes a comparison column, not the answer.

Assisted results removed, not discounted

If a human sold it, it comes off entirely. A clean small number beats a padded big one.

If it needs a scroll bar, it is a report

One screen, one glance, one decision. Anything more belongs to the person running the ads, not the CEO reading them.

Six problems, none exotic, all worth naming.

The dashboard took an afternoon. Almost all of that afternoon went on the six problems below, each one a place a generic tool would have shown a confident, wrong number.

●

Double-counted leads

The platform's lead figure merges on-Facebook form leads with website pixel leads, 21 reported, 8 real. Fixed by splitting the count per ad set type so each only counts the kind of lead it was actually built to produce.

●

Purchases the ads did not earn

Six attributed purchases, three of them phoned in by the sales team. Fixed by counting orders in the store and removing any whose customer phone number appears in the sales team's channel.

●

Ad set filed in the wrong tab

A purchase-optimised landing-page ad set landed in the holiday tab because setup rules ran before name rules. Fixed by checking names across all funnels first, only falling back to setup when the name says nothing.

●

Sales posts had no order numbers

The automated sales posts contain a name and a phone number, nothing else, so matching on order numbers found nothing. Fixed by matching on the last nine digits of the phone number, so every format agrees.

●

The store said no

The store's API returned 403 to the first requests, a security layer rejecting calls with no browser user-agent. Fixed by sending a proper user-agent, falling back to query-string authentication, and surfacing the real error text on the tile.

●

The ad platform's hourly limit

Redeploying and refreshing repeatedly during the build exhausted the hourly API allowance. Fixed with a shared cache, ten-minute reuse, a two-minute floor on manual refresh, and stale numbers shown with a note instead of an error.

The pattern behind all six: each one is a place where a generic tool would have shown a confident, wrong number, because it only reads the ad platform. The fixes all come from knowing the business, that a sales team exists, that a chat is the lead, that the store is the source of truth for a sale, and that closes get posted with a phone number rather than an order ID.

Three systems reconciled, one screen, no vendor.

Component Technology Purpose Cost
Backend and dashboard Cloudflare Worker (single JS file, no framework) Fetches all three sources, reconciles them, and renders the dashboard Free
Caching Cloudflare KV Shared cache with ten-minute reuse, stale-while-revalidate on API limits Free
Ad performance data Meta Marketing API, system user token Spend, impressions, link clicks, every action type, per ad set per day Existing
Sales data Online store REST API Completed orders, products, totals, customer phone number Existing
Sales-assist detection Team chat bot, read-only Reads the sales channel's close posts, matched by phone number to store orders Existing

The one question a founder actually asks, answered in a minute.

Built in an afternoon, using systems the business already had, its ad account, its store, its team chat, its existing hosting. No new vendor, no monthly fee, no data leaving the company. The dashboard runs at a single URL behind a key and is read on a phone before the first coffee.

What it needs in place elsewhere is short: a Meta Business Manager with a system user, read access to wherever real results live, somewhere sales-assisted results are recorded even if it is just a chat channel, and one clear answer from the founder to what the one number per funnel actually is.

The question worth asking any team running paid ads: for each funnel, what did a result cost last week, counted in the business's own system, with sales-assisted results removed? If the answer takes more than a minute to produce, the reporting is working for the ad platform, not for the business.