How to Automate Ad Account Health Monitoring
Monitor ad account health metrics and get alerts before issues impact performance.
Jay Banlasan
The AI Systems Guy
Ad account health monitoring with automated alerts catches problems before they snowball. I have seen accounts get restricted, pixels break, and billing fail silently. By the time someone noticed, the damage was done. This system checks everything daily and pings you the moment something is off.
The checks take seconds to run. The problems they catch save days of lost performance.
What You Need Before Starting
- Meta Ads API with system user access
- Python 3.8+ with
requestsinstalled - Slack webhook for notifications
- A list of accounts to monitor
Step 1: Define Health Checks
HEALTH_CHECKS = {
"account_status": True,
"payment_method": True,
"pixel_health": True,
"spend_anomaly": True,
"delivery_issues": True,
"policy_violations": True,
}
ACCOUNTS = [
{"id": "act_123456789", "name": "Client A", "expected_daily_spend": 50},
{"id": "act_987654321", "name": "Client B", "expected_daily_spend": 100},
]
Step 2: Check Account Status and Billing
import requests
import os
def check_account_status(account_id):
token = os.getenv("META_ACCESS_TOKEN")
resp = requests.get(f"https://graph.facebook.com/v19.0/{account_id}",
params={"access_token": token, "fields": "account_status,disable_reason,name,balance,currency"})
data = resp.json()
issues = []
status = data.get("account_status")
# 1=Active, 2=Disabled, 3=Unsettled, 7=Pending Review, 9=In Grace Period
status_map = {1: "Active", 2: "Disabled", 3: "Unsettled", 7: "Pending Review", 9: "Grace Period"}
if status != 1:
issues.append({
"check": "account_status",
"level": "critical",
"message": f"Account status: {status_map.get(status, 'Unknown')} (code {status})"
})
if data.get("disable_reason"):
issues.append({
"check": "disable_reason",
"level": "critical",
"message": f"Disable reason: {data['disable_reason']}"
})
return issues
Step 3: Check Pixel Health
def check_pixel_health(pixel_id):
token = os.getenv("META_ACCESS_TOKEN")
resp = requests.get(f"https://graph.facebook.com/v19.0/{pixel_id}",
params={"access_token": token, "fields": "name,last_fired_time,is_unavailable"})
data = resp.json()
issues = []
if data.get("is_unavailable"):
issues.append({
"check": "pixel_health",
"level": "critical",
"message": f"Pixel {pixel_id} is unavailable"
})
last_fired = data.get("last_fired_time")
if last_fired:
from datetime import datetime
last_fire_dt = datetime.fromisoformat(last_fired.replace("Z", "+00:00"))
hours_since = (datetime.now(last_fire_dt.tzinfo) - last_fire_dt).total_seconds() / 3600
if hours_since > 24:
issues.append({
"check": "pixel_health",
"level": "warning",
"message": f"Pixel has not fired in {hours_since:.0f} hours"
})
return issues
Step 4: Check Spend Anomalies
import sqlite3
def check_spend_anomaly(db_path, account_id, expected_daily):
conn = sqlite3.connect(db_path)
yesterday_spend = conn.execute("""
SELECT COALESCE(SUM(spend), 0) FROM ad_daily
WHERE account_id = ? AND date = DATE('now', '-1 day')
""", (account_id,)).fetchone()[0]
avg_spend = conn.execute("""
SELECT COALESCE(AVG(daily_spend), 0) FROM (
SELECT date, SUM(spend) as daily_spend FROM ad_daily
WHERE account_id = ? AND date >= DATE('now', '-7 days')
GROUP BY date
)
""", (account_id,)).fetchone()[0]
conn.close()
issues = []
if yesterday_spend == 0 and expected_daily > 0:
issues.append({
"check": "spend_anomaly",
"level": "critical",
"message": f"Zero spend yesterday. Expected ~${expected_daily}/day"
})
elif avg_spend > 0 and yesterday_spend > avg_spend * 2:
issues.append({
"check": "spend_anomaly",
"level": "warning",
"message": f"Spend spike: ${yesterday_spend:.2f} vs ${avg_spend:.2f} 7-day avg"
})
elif avg_spend > 0 and yesterday_spend < avg_spend * 0.3:
issues.append({
"check": "spend_anomaly",
"level": "warning",
"message": f"Spend drop: ${yesterday_spend:.2f} vs ${avg_spend:.2f} 7-day avg"
})
return issues
Step 5: Run All Checks and Alert
def run_health_check(db_path):
all_issues = []
for account in ACCOUNTS:
account_issues = []
status_issues = check_account_status(account["id"])
account_issues.extend(status_issues)
spend_issues = check_spend_anomaly(db_path, account["id"], account["expected_daily_spend"])
account_issues.extend(spend_issues)
if account_issues:
all_issues.append({"account": account["name"], "issues": account_issues})
if all_issues:
send_health_report(all_issues)
else:
print("All accounts healthy")
return all_issues
def send_health_report(issues):
webhook = os.getenv("SLACK_WEBHOOK_URL")
message = "*Ad Account Health Report*\n\n"
for account in issues:
message += f"*{account['account']}*\n"
for issue in account["issues"]:
icon = "🔴" if issue["level"] == "critical" else "🟡"
message += f" {icon} {issue['message']}\n"
message += "\n"
requests.post(webhook, json={"text": message})
# Run twice daily
0 7,15 * * * cd /path/to/project && python3 health_check.py
Morning and afternoon checks cover most failure modes. Critical issues like disabled accounts get flagged immediately.
What to Build Next
Add billing health checks that verify payment methods are valid. Then build a weekly health summary that trends account health over time so you can spot gradual degradation.
Related Reading
- Why Monitoring Is Not Optional - the foundation of operational monitoring
- The Single Point of Failure Problem - eliminating single points of failure
- Designing for Failure - building systems that handle failures gracefully
Want this system built for your business?
Get a free assessment. We will map every system your business needs and show you the ROI.
Get Your Free Assessment