Anomaly Sentinel watches an account hour by hour. It compares today so far (through the last complete hour) with the same weekday over the past N weeks and raises WARNING or CRITICAL alerts when a metric drifts beyond a robust z-score. Alerts are de-duplicated across hours, escalated when they get worse and closed with a recovery notice.

How it works

For each of ten metrics (impressions, clicks, cost, conversions, conversion value, CTR, CPC, conversion rate, CPA, ROAS) the script builds a baseline from the same weekday over weeks weeks, uses median and MAD instead of mean and standard deviation so single outliers do not distort it, and applies absolute floors when the baseline is zero. Volume gates (min_impressions, min_clicks), quiet hours, exception dates and a minimum percentage deviation suppress noise. Because conversions arrive late, conversion-based metrics are capped at WARNING until an hour-of-day snapshot baseline has at least snapshot_baseline_min_samples samples; after that they switch to the snapshot baseline. Each alert carries the top campaigns explaining the deviation.

What it writes to the Sheet

  • Alerts — 14 columns per event: Timestamp, Date, Hour, Metric, Status (NEW / ESCALATED / RECOVERED), Severity (WARNING / CRITICAL), Observed, Expected, Deviation, Samples, Campaign hints and more; the delivery channel is stored as a note on the Status cell.
  • _settings — every threshold below, editable without touching the code.
  • _state, _baseline, _snapshot — internal: open alerts, the weekday baseline and the hour-of-day snapshots (pruned after snapshot_keep_weeks weeks).

Setup

  1. Create a new, empty Google Sheet with the Google account you will authorise the script with and copy its URL. Do not reuse a Sheet from another script — each Sheet is bound to one script and one Google Ads account.
  2. In Google Ads open Tools → Scripts, create a new script, paste the whole code and give it a name.
  3. Set CONFIG.spreadsheet_url to the Sheet URL. If you run the script from a manager (MCC) account, also set CONFIG.account_id to the client account ID (e.g. 123-456-7890); without it the script stops with a clear error.
  4. Optional: fill CONFIG.email (comma-separated recipients) and/or CONFIG.slack_webhook. Keep the webhook in the code only — it is never written to the Sheet.
  5. Authorise the script, run Preview once to check the log, then Run. The first run creates the tabs, the header rows and the _settings tab.
  6. Schedule it: hourly.
  7. Optional: download the Excel dashboard (button above), open its Read me tab and paste the Sheet tabs as described — the column layout matches the script output one-to-one.

Parameters

ParameterDescriptionDefault
spreadsheet_urlURL of the empty Google Sheet the script writes to.'YOUR_SPREADSHEET_URL'
emailComma-separated recipients; blank = no e-mail. Can be overridden in _settings.''
slack_webhookSlack incoming-webhook URL; blank = no Slack message. Lives only in the code.''
account_idClient account ID; required only when running from a manager (MCC) account.''
weeksSame-weekday history depth.12
lag_hoursReporting-delay tolerance in hours; the last N hours are not evaluated.3
quiet_hours_endNo rate-based alerts before this hour.6
min_impressions / min_clicksVolume gates for rate metrics.1000 / 50
min_conversions_baselineSkip CPA / conv. rate / ROAS alerts when baseline conversions are below this.3
z_warning / z_criticalRobust z-score thresholds for WARNING and CRITICAL.2.0 / 3.0
min_pct_deviationIgnore deviations smaller than this share.0.20
abs_floorAbsolute thresholds used when the baseline is zero (impressions, clicks, cost, conversions, value).200 / 20 / 20 / 2 / 50
conversion_metrics_max_severityWARNING or CRITICAL — cap for conversion-based metrics until the snapshot baseline matures.'WARNING'
snapshot_baseline_min_samplesSamples needed before conversion metrics use the hour-of-day snapshot baseline.12
snapshot_keep_weeksWeeks of hourly snapshot rows kept before pruning.26
exception_datesDates to ignore (holidays, launches), yyyy-MM-dd.[]
campaign_hintsTop-N campaigns listed as the explanation of a deviation.3
All values live in the CONFIG block at the top of the script; keys that also exist in the _settings tab can be changed there.

The _settings tab

On the first run the script creates a _settings tab (Key / Value / Notes). Values there override the matching CONFIG entries, so day-to-day tuning happens in the Sheet without editing code. Numbers must be written without thousands separators and with at most two decimals (0.5 or 0,5). The rows script and account mark the Sheet’s owner: if another script or another account opens the same Sheet, it stops before writing anything. Internal tabs starting with an underscore (_settings, _history, _snapshot, _state, _baseline) are language-independent, so switching between the EN/TR/DE builds keeps the stored state.

Notifications

New, escalated and recovered events go out by e-mail and/or Slack with metric, observed vs expected, deviation and campaign hints. If neither channel delivers, the alert is not written and the state is rolled back, so the next hour retries with a single record instead of a duplicate. Partial delivery (one channel failed) is logged.

Notes and limits

  • In the first 12 weeks conversion alerts stay at WARNING by design — this is the snapshot baseline maturing, not a bug.
  • Date arithmetic is calendar-based, so daylight-saving switches (US and EU) do not shift the weekday baseline.
  • Every report tab carries a small attribution cell (H1:K1, warning-only protection) and the code header keeps a copyright line; both may be removed by anyone who edits the source.