Identify ad creative fatigue by monitoring performance decay and replace or rotate assets once key metrics breach predefined thresholds.
Audit & Continuous Testing Pipeline
1. Data extraction (last 30 days)
- Meta: GET /{ad_id}/insights?fields=impressions,clicks,spend,ctr,cvr,roas&time_range={"since":"2024-07-01","until":"2024-07-31"}
- Google: SELECT segments.date, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions FROM ad_group_ad WHERE segments.date DURING LAST_30_DAYS
2. Calculate fatigue score
fatigue = (CTR_7d_avg / CTR_30d_avg) * (CPC_7d_avg / CPC_30d_avg) – score < 0.85 flags fatigue.
3. Thresholds
- Meta: CTR drop > 15 % or CPC rise > 20 % → fatigue.
- Google: CVR drop > 12 % or ROAS decline > 10 % → fatigue.
4. Automated alert – set up CloudWatch Event (or Google Cloud Scheduler) to run the Python script daily; push to Slack via webhook.
5. Creative rotation
- If fatigue flagged, duplicate ad set, swap creative_id with a fresh asset, keep budget and targeting unchanged.
6. A/B test queue
- Create a “test pool” of 3‑5 variants per ad set, allocate 10 % of spend, run for 5‑7 days, evaluate using Bayesian uplift (beta-binomial).
7. Continuous learning
- Store results in BigQuery table ad_fatigue_log; schedule a Looker Studio dashboard to surface trends and auto‑adjust the 10 % test allocation based on last‑week lift > 5 %.
Metric comparison
| Platform | Fatigue trigger | Primary KPI |
|----------|----------------|------------|
| Meta | CTR ↓ 15 % or CPC ↑ 20 % | ROAS |
| Google | CVR ↓ 12 % or ROAS ↓ 10 % | CPA |
Python snippet (Meta & Google)
import requests, pandas as pd
def fetch_meta(ad_id, token):
url = f"https://graph.facebook.com/v18.0/{ad_id}/insights"
params = {"fields":"impressions,clicks,spend,ctr,cvr,roas",
"time_range":'{"since":"2024-07-01","until":"2024-07-31"}',
"access_token": token}
return pd.DataFrame(requests.get(url, params=params).json()["data"])
def fetch_google(client):
query = ("SELECT segments.date, metrics.impressions, metrics.clicks, "
"metrics.cost_micros, metrics.conversions FROM ad_group_ad "
"WHERE segments.date DURING LAST_30_DAYS")
response = client.search_stream(query=query)
return pd.DataFrame([row.to_dict() for row in response])