---
id: agencias/guardiao-de-midia
industry: Marketing agencies
usage: common
effort: medium
grain: Alert by client x type x network x object x day
---

# Media watchdog

Every morning, the short list of what broke across the portfolio: accounts that dropped to zero, campaigns that stopped converting, accounts with no adjustments, balances running out and stalled sources.

## Executive summary

**For:** Agencies with many media accounts and a team that cannot check all of them every day.

**What it solves**

- The agency learns about a problem before the client does.
- Alerts fire only on real problems, not because of a late source or the wrong objective.
- Prepaid accounts do not stop for lack of balance without warning.

**Why it matters:** A campaign stalled for days is the client's money going to waste, and when the client notices first, trust in the agency drops. A reliable watchdog frees the team from checking account by account and turns each problem into a task with an owner the same day.

**Business questions it answers**

- Which accounts dropped to zero yesterday?
- Which clients have had no adjustments for more than 7 days?
- Which prepaid accounts run out of balance this week?
- Is any source late and possibly hiding a problem?
- Which campaign stopped converting and how much has it spent since?

**What you need**

- Portfolio media in a daily table with the client on each row
- Account-to-client registry with the official accounts and their status
- A delivery channel (Chat, Slack, Discord) or task manager

**What you get**

- Daily alerts table with key and state
- Balance monitor for prepaid accounts with days until empty
- Source freshness monitor
- Daily AI-written message for leadership

## Detailed

### The problem

With dozens of accounts, nobody checks all of them every day. The agency finds out about the paused campaign, the account with no balance or the conversions that dropped to zero when the client complains. And a badly built alert is worse: it fires every day because of a late source or a reach campaign with no leads, and the team learns to ignore it.

### Business rules

1. **A stalled source is not a stalled network.** Before saying spend dropped to zero, check that the source synced, using its run in Nekt. Without a sync, the alert is FONTE_SEM_SYNC. Only with a live source does zero spend become REDE_ZERADA. Do not generate performance alerts on a degraded source. (Avoid: Alerting zero spend from a connector that did not run.)
2. **Delay tolerance follows the source cadence.** Delay = expected date minus the latest date in the table. Tolerances used: Meta and Google D-1; LinkedIn and GA4 D-2; marketing automation D-3. Weekly source: interval between extractions plus 2 days of slack (9 days). If the cadence changes, the limit changes with it. An account intentionally paused in the registry does not alert. (Avoid: A fixed 2-day limit for every source.)
3. **Judge by optimization, not by name.** Reach, traffic or video campaigns are not judged on conversions. What defines it is the ad set's optimization_goal and the optimized event, not TOFU, MOFU or BOFU in the campaign name. (Avoid: A zero-leads alert on a reach campaign.)
4. **Dropped to zero means it stopped converting while spending.** CAMPANHA_ZEROU only when there is spend in the recent window, zero conversions of the optimized event and conversions in the earlier history. The message includes network, spend since it stopped and the history. REDE_ZERADA is a network with budget in the plan and no spend. (Avoid: Calling a campaign that never converted, or has no spend, dropped to zero.)
5. **Short windows against the baseline.** Compare the 7 days through yesterday with the previous 7 and with the 28-day baseline. Always show leads per day over the last 7, 15 and 30 days. The reference day is yesterday, the last full day in the client's time zone. (Avoid: Looking only at the 30-day total.)
6. **Human adjustments exclude automatic ones.** No adjustment = a client with spend in the last 7 days and no human change for more than 7 days, evaluated per network. Meta: actor_name other than 'Meta' in the change history. Google: interface, Editor, app, applied recommendations and bulk upload; scripts, automated rules and API are left out. (Avoid: Counting any event in the history as an adjustment.)
7. **Balance follows the account semantics.** On a prepaid account (is_prepay_account true), balance is remaining credit; on a postpaid account, it is the amount owed. Store the raw balance under a neutral name and derive available balance only when Meta exposes it. Days until empty = available balance / average daily spend over the last 7 days. If there is a fee on Pix (instant payment) top-ups (e.g. 12.15%), the net balance is lower than the amount paid. (Avoid: Treating balance as available funds on every account.)
8. **Only the client's official accounts.** A source's accounts table lists every account the credential can see in Business Manager, including closed and third-party accounts. Filter by the official account_id in the account-to-client registry. An account with account_status other than active and zero spend with a live source becomes a payment alert. (Avoid: Monitoring the balance of every account in the table.)
9. **Alerts with a key and state.** Stable key client|type|network|object. Persistent state: new, recurring, resolved. The same alert is not resent; every day it is checked whether it cleared. Optimization tips wait 7 days to mature before being reassessed. (Avoid: Sending the whole list every day.)
10. **Rules in SQL, text by AI.** Detection is SQL over the day's tables. The LLM only writes the message from the alerts, with network, amount and what to check first. If the alerts table is not from today, the message warns that the data is stale. (Avoid: Asking the model to find anomalies in raw data.)

### Alert types

Evaluated in order: first the source, then the account, then performance.

- **FONTE_SEM_SYNC** → Source not syncing (`delay above tolerance or last run failed`)
- **CONTA_COM_PROBLEMA** → Payment issue or blocked account (`account_status not active`)
- **SALDO_BAIXO** → Balance running out (`balance / 7d average spend below N days`)
- **REDE_ZERADA** → Network with budget and no spend (`live source and zero spend`)
- **CAMPANHA_ZEROU** → Stopped converting (`recent spend, zero conversions, past conversions`)
- **QUEDA_RESULTADO** → Drop against the baseline (`7d vs previous 7d and 28d baseline`)
- **RITMO_VERBA** → Budget pacing off plan (`cumulative spend vs budget prorated by days`)
- **SEM_AJUSTE** → No human adjustment (`spend in 7d and no human change in over 7d`)
- **REACH, TRAFFIC, VIDEO** → Not judged on conversions (`evaluate by CPM, reach and frequency`)

### Definitions

| Metric | Definition |
|---|---|
| Reference day | yesterday, the last full day in the client's time zone |
| 7d window | the 7 days through yesterday; previous 7d is the 7 days before it; baseline is the last 28 days |
| Late source | expected date minus the latest date in the table, above the source's tolerance |
| Human adjustment | a change history event made by a person; on Meta, actor_name other than 'Meta' |
| Available balance | remaining credit on a prepaid account as reported by Meta; never derived from a postpaid account's balance |
| Days until empty | available balance / average daily spend over the last 7 days |
| Open alert | key client|type|network|object present on the day; resolved when it disappears on reassessment |

### Pitfalls

- **A paused campaign nobody noticed.** The client had normal lead volume for the month, but the daily view showed six days in a row with no leads at all. How to avoid: Leads per day over 7, 15 and 30 days, always.
- **Constant alarm.** The sources switched to running once a week and the 2-day limit flagged every account. The team stopped reading the alert. How to avoid: A limit derived from the cadence: interval plus 2 days.
- **A zero that was a stalled connector.** The network showed zero because the source had not run, and the team went to change the campaign. How to avoid: Check the source run before the network alert.
- **Balance from an account that is not the client's.** The accounts table brought the whole Business Manager, with closed and third-party accounts in the balance monitor. How to avoid: Filter by each client's official account_id.
- **Expired card.** The account's card expired and the ads stopped silently for days. How to avoid: Alert on account status and zero spend with a live source.
- **The same tip every day.** Optimization tips repeated daily turned into noise before anyone acted. How to avoid: Stable key, one task per tip and 7 days to mature.

Validated on real data: In Nekt's own Meta Ads account, 27% of one week's events in the change history had actor_name 'Meta' (automatic changes to delivery status, first delivery and billing). Counted as adjustments, they would make an idle account look well tended. In the same source, the accounts table lists 9 Business Manager accounts, with prepaid, postpaid and closed accounts mixed together, and only 1 of them has delivery.

### Acceptance test

- [ ] On a day a source did not run, its client shows FONTE_SEM_SYNC and no performance alerts for that network.
- [ ] No campaign optimized for reach, traffic or video appears in CAMPANHA_ZEROU.
- [ ] Reprocessing the same day does not resend alerts already sent, and an alert that disappeared is marked resolved the next day.
- [ ] For a prepaid account, the balance matches the billing screen in Ads Manager, and days until empty = balance / average spend over the last 7 days.
- [ ] The number of accounts in the balance monitor equals the number of official accounts in the account-to-client registry.

### Context document

```text
Media watchdog
- Reference day = yesterday, the last full day in the client's time zone.
- Before any performance alert, check the source. Delay above tolerance (Meta and Google D-1, LinkedIn and GA4 D-2, marketing automation D-3, weekly source interval + 2 days) = FONTE_SEM_SYNC, and that network's performance is not analyzed.
- Zero spend with a live source and budget in the plan = REDE_ZERADA.
- CAMPANHA_ZEROU = recent spend, zero conversions of the optimized event and conversions in the history. Report network, spend and history.
- Reach, traffic and video campaigns (by optimization_goal, not by name) are not judged on conversions.
- Compare 7 days against the previous 7 and against the 28-day baseline. Always show leads per day over 7, 15 and 30 days.
- No adjustment = spend in the last 7 days and no human change for more than 7 days. On Meta, human means actor_name other than 'Meta'.
- Balance only for the official accounts in the registry. Prepaid: balance is credit; postpaid: balance is amount owed. Days until empty = balance / 7-day average spend.
- Each alert has the key client|type|network|object and a state (new, recurring, resolved). Do not repeat an alert already sent.
Client exceptions:
- <client>: shared account; only campaigns with <term> in the name are included.
- <client>: prepaid account topped up via Pix with a <x>% fee; warn when <n> days of balance are left.
- <client>: <platform> source runs weekly; tolerance of <n> days.
```

### Variations

- **Plan vs. actuals:** Alerts per plan line: unlinked, no delivery, out of period, off plan and budget pacing.
- **Optimization tips:** Spend leaking without conversions, high CPA, scale up and creative fatigue, with 7 days to mature.
- **Balance on Google Ads:** Account budget and projected end of month based on spend pace.
