Devlly

get in touch

Devlly - a software studio. We automate business: from a Telegram bot to a full CRM/ERP system.

Get updates

  • Case
  • Marketing and analytics

NovaFit - Google Ads and Meta Ads performance monitoring

NovaFit runs ads on Google Ads and Meta Ads at the same time, and before this system the campaigns were checked by hand - in two ad accounts, one after the other, whenever somebody found the time. Now every morning the system pulls yesterday’s numbers from both ad accounts on its own, drops them into a Google Sheet with a ready-made dashboard, compares every campaign with its own median for the week and, if CPA or ROAS crossed a threshold, says so in Telegram. An expensive day shows up the next morning, not at the end of the month.

Who it fits: any business spending real money on ads across two or more channels - online stores, fitness clubs, schools, clinics, subscription services. Especially those with no analyst on staff whose agency sends a report once a month.

Stack: Python, connectors to the Google Ads API and the Meta Marketing API, the Google Sheets API as both storage and dashboard, aiogram for the Telegram bot and APScheduler for the daily run.

  • Python
  • Google Ads API
  • Meta Marketing API
  • Google Sheets API
  • aiogram
  • APScheduler

How the system is put together

Three parts, one sheet. The Google Sheet is both the storage and the dashboard: the daily campaign rows land in it, and the styling, formulas and charts live right there. The detection module reads those same rows and decides whether anything deviates. The Telegram bot shows summaries on request and sends out new alerts. Every morning at 09:00 Kyiv time the scheduler runs the whole chain: fetch yesterday from Google Ads and Meta Ads - write - recalculate - check - notify.

The numbers come straight from the ad accounts: a Google Ads API connector pulls the daily campaign metrics from Google, a Meta Marketing API connector pulls them from Meta. Both sit behind one interface with a single method - return the metric rows for a period - so a third channel, say TikTok Ads, is one more class, while the storage, detection, bot and sheet styling stay untouched.

1. The dashboard in a Google Sheet

The dashboard sheet is laid out for a 1920x1080 screen so it can be opened on an office monitor or TV without scrolling. At the top - five KPIs for yesterday (spend, conversions, CPA, ROAS, CTR), each with its deviation from the 7-day median. Below - a channel summary and a campaign table: spend, clicks, CTR, conversions, CPA, ROAS, the change against the previous week and a status of either normal or critical.

Then four daily charts - spend, CPA, ROAS and conversions, each split into Google / Meta / total - and the log of recent alerts. There is no separate BI tool and no licence: the client opens an ordinary sheet in their own account and can filter, copy or export whatever they need. The locale is Ukrainian: amounts in hryvnia, a decimal comma, dd.mm.yyyy dates.

Google Ads and Meta Ads performance dashboard in a Google Sheet - KPIs, channels, campaigns
The top of the dashboard: five KPIs for yesterday with the deviation from the 7-day median, a channel summary and a campaign table with a status. A campaign that crossed a threshold is highlighted red right in its row.
Daily charts of spend, CPA, ROAS and conversions plus the log of recent alerts
The bottom of the dashboard: four daily charts - spend, CPA, ROAS and conversions by channel - and the alerts for the week, each with its level, metric, value against the median and message text.

2. Data and formulas

Under the dashboard sit three working sheets. The data sheet holds one row per campaign per day with every metric; revenue and the alert flag are calculated by formulas rather than written by code, so any figure can be checked with a click. The daily sheet rolls those same rows up into per-day aggregates for Google, Meta and both. The alerts sheet is the anomaly log with the date, level, value, median and message text.

The data sheet - one row per campaign per day with spend, conversions, CPA and ROAS
The data sheet: one row per campaign per day - spend, impressions, clicks, CTR, conversions, CPA and ROAS. Revenue and the alert flag are formulas, so the sheet can be filtered and checked like any other.

3. How anomalies are caught

For every campaign and every day the system takes the median CPA and ROAS of the 7 previous days - the current day is left out of the window - and compares the current value against it. The median rather than the mean on purpose: one anomalous day inside the window does not shift the baseline, so the system never gets used to bad numbers.

There are two thresholds. Warning - CPA 60 % above the median or ROAS 40 % below, and only if it holds for two days in a row: one bad day is not yet an incident. Critical - CPA 120 % above or ROAS 60 % below, fires immediately; zero conversions with spend is critical too. One alert per campaign per day: CPA is the primary metric, ROAS the secondary.

An incident in the sheet: a Meta Ads campaign flagged critical on CPA and ROAS three days in a row
This is what an incident looks like in the data: on 9 September the cold-audience campaign’s CPA rose 67 % above its median - a yellow warning - and from 10 to 12 September it stayed at +130…+190 % with ROAS below 1.3x - three red criticals in a row.

4. The Telegram bot

The bot is what the owner sees every day. After /start the chat is subscribed to alerts and receives them automatically after the morning update. /report shows yesterday’s summary per channel with ✅ and ⚠️ marks against the median, /alerts lists every anomaly from the last 14 days. Every message carries an

The ad monitoring Telegram bot - commands, alert subscription and the first critical notification
After /start the chat is subscribed to alerts. Every notification carries the level, the channel, the campaign, the value against the median and a link to the dashboard.
The daily ad report in Telegram - spend, conversions, CPA and ROAS by channel with deviation from the median
/report - yesterday’s summary per channel: spend, impressions, clicks, CTR, conversions, CPA and ROAS marked ✅ or ⚠️ against the median, the biggest campaign and the number of alerts for the day.
The 14-day list of CPA and ROAS anomalies in the Telegram bot with warning and critical levels
/alerts - every anomaly from the last 14 days, newest first, with its level and explanation. The rules they fire on are printed at the bottom so nobody has to remember the thresholds.

The logic that matters

  • The baseline is the 7-day median of each campaign on its own, not the account average: an expensive campaign cannot hide behind a cheap one
  • Two levels with different sensitivity: a warning needs a second day to confirm, a critical fires at once - less noise, yet nothing serious slips through
  • Deduplication by date and campaign: only new alerts are sent, a re-run never spams old ones
  • A Google Sheet instead of BI: no licences, the data stays in the client’s account, the formulas can be audited
  • Formulas are written in code in en_US syntax and translated to the sheet’s locale before writing - the sheet stays Ukrainian
  • Google Ads and Meta Ads behind one interface: the two API connectors are interchangeable, and a new channel is one more class with no changes elsewhere

What the system can do

  • Automatic daily metric pull from the Google Ads API and the Meta Marketing API at 09:00 Kyiv time
  • A Google Sheet dashboard laid out for 1920x1080: KPIs, channels, campaigns, four charts, recent alerts
  • Every metric compared with its 7-day median right in the KPI tiles and tables
  • A campaign status of normal or critical with the row highlighted
  • A data sheet with one row per campaign per day and formulas for revenue and the alert flag
  • Daily aggregates for Google, Meta and both on a separate sheet
  • An alert log with the level, metric, value, median, deviation and the time it was recorded
  • CPA and ROAS anomaly detection against a rolling median with two levels and a two-day rule for warnings
  • A Telegram bot: /report for yesterday by channel, /alerts for 14 days, subscribe and unsubscribe
  • Automatic delivery of new alerts only, after every refresh
  • A CLI for manual operations: load history, add a day, check, send

Want the same control over your ad spend? Get in touch