---
id: agencias/performance-de-criativos
industry: Marketing agencies
usage: common
effort: medium
grain: Ad x day (curve) and ad over the period (ranking)
---

# Creative performance

Which ad holds attention, which one converts and which one is wearing out, by client, with the creative image next to the number.

## Executive summary

**For:** Agencies that produce and test creatives for several clients and need to say which one works.

**What it solves**

- The best creative is picked by the campaign objective, not by everyone's CTR.
- Hook rate and hold rate have one definition per client and add up correctly in any cut.
- The tired creative shows up before cost rises, with a thumbnail that does not break on the dashboard.

**Why it matters:** Creative is what the agency produces most and can prove least. Without a yardstick, each analyst picks the best ad by a different criterion and the client gets a different recommendation every month. And fatigue only shows up when cost per result has already gone up.

**Business questions it answers**

- What is the best creative in each campaign this month?
- Which videos hold attention after the first seconds?
- Which ads are worn out and need to be replaced?
- Video, carousel or static: what converts best for this client?
- Should we produce more short or long videos?

**What you need**

- Meta Ads with ads, creatives and images synced, plus TikTok or Google when present
- The hook rate and hold rate definition agreed with the creative team
- A place to store each ad's thumbnail with a stable URL

**What you get**

- A daily curve per ad with age, frequency and cumulative impressions
- Creative ranking by objective, with thumbnail and link
- Tired creative alert with a stable key
- An AI agent that answers which creative to replace and why

## Detailed

### The problem

Meta delivers video metrics as lists inside each row, the thumbnail URL expires at every sync and the same name shows up on different ads. Each agency defines hook rate its own way, and whoever stores the rate per day and averages it later delivers a number that matches no cut. Without the daily curve there is no fatigue, only a total.

### Business rules

1. **The key is ad_id.** Aggregate by ad_id, never by ad_name: the same name appears on different ads, and merging them changes the ranking. The name is for display and for extracting attributes agreed with the creative team (format, length, script number) by position in a standard naming convention. (Avoid: Grouping by ad_name.)
2. **Hook and hold with a declared definition.** Pick one definition per client and keep it. The most common: hook rate = 3-second views (action_type video_view) / impressions, or 25% views / impressions; hold rate = ThruPlay or 15 seconds / 3-second views, or 100% views / 25% views. Write in the context which one was chosen. (Avoid: Switching definitions between clients and comparing the numbers.)
3. **Store numerator and denominator.** Rates are not stored per row. The table keeps impressions, plays, 25, 50, 75, 95 and 100% views, ThruPlay and clicks, and the rate is computed at read time from period sums. Averaging daily rates gives the wrong number when aggregating by week or campaign. (Avoid: Storing hook_rate per day and averaging it.)
4. **Quartiles have a different base per platform.** On Meta, video metrics are lists (video_p25_watched_actions and similar) and the quartile rate uses video_plays as the base. On YouTube, the quartile rate is over impressions, and Google delivers a rate, not a count: rebuild the count by multiplying by views before combining. TikTok has 2 and 6-second views and p25 to p100. (Avoid: Comparing Meta quartiles directly with YouTube quartiles.)
5. **Best creative by objective.** Compare creatives within the same campaign or objective: cost per result for conversion, CPM for reach, CPC or cost per landing page view for traffic, cost per ThruPlay for video. Only creatives with spend in the period and a declared minimum of impressions count. Cost in a reach campaign reflects frequency, not the creative. (Avoid: One ranking by CTR for every ad.)
6. **Format by delivery.** If the ad recorded video plays on any day, it is a video, even if the creative says SHARE (boosted post) or has no video_id. Evaluate over the ad's whole history, not per day, or a day with no plays reclassifies the same creative. (Avoid: Trusting only the creative's object_type or video_id.)
7. **Fatigue is a curve, not a total.** Keep the table by ad and day with days since launch (first day with impressions), frequency and cumulative impressions. Fatigue is results falling while saturation rises. A rule used in practice: last 7 days CTR at least 35% below the previous 7, with 5,000 impressions or more in both windows. (Avoid: Only the month's ranking, with no time axis.)
8. **Thumbnail with a stable URL.** Meta image URLs are temporary and reissued at every sync. The creative's thumbnail_url and image_url are often empty: get the image from adimages, linked through the creative (adimages.creatives contains the ad's creative.id). For an external dashboard, copy the image right after the sync and store ad_id and your own URL. (Avoid: Storing Meta's URL in the dashboard.)
9. **No preview is not an ad without art.** Carousels and videos are almost never in adimages; a sponsored post that never went to the feed has no embed; a LinkedIn sponsored message has no image or impressions and is compared by sends and opens; a Google ad brings no text or image through the connector. Say it is a source limitation and never make up the ad text. (Avoid: Flagging it as a creative with no content.)
10. **Cost with or without tax, declared.** If the client reports Meta with a tax markup on spend, CPM, CPC and cost per view in the creative report use the same criterion as the media report. Mixing the two makes Meta video look more expensive than YouTube. (Avoid: Spend with tax in the total and without tax in the ranking.)

### Attention metrics

Pick one hook definition and one hold definition and declare them in the client's context.

- **Meta · video start** → 3-second view (`actions: video_view`)
- **Meta · playback** → Plays (`video_play_actions`)
- **Meta · quartiles** → 25%, 50%, 75%, 95% and 100% (`video_p25_watched_actions to video_p100_watched_actions`)
- **Meta · 15 seconds or complete** → ThruPlay (`video_thruplay_watched_actions`)
- **Hook rate** → Initial attention (`video_view / impressions or p25 / impressions`)
- **Hold rate** → Retention (`ThruPlay / video_view or p100 / p25`)
- **YouTube · quartiles** → Rate over impressions (`metrics_video_quartile_p25_rate x views`)
- **TikTok · start** → 2 and 6-second views (`video_watched_2s, video_watched_6s`)

### Definitions

| Metric | Definition |
|---|---|
| Hook rate | sum of the declared initial attention (3-second video_view or p25) / sum of the ad's impressions in the period |
| Hold rate | sum of ThruPlay (or p100) / sum of initial attention (video_view or p25), over the same period |
| Full retention | sum of p100 / sum of impressions |
| Quartile rate | Meta: pXX / video_plays; YouTube: pXX / impressions |
| Days since launch | date minus the ad_id's first day with impressions |
| Period frequency | impressions / reach for the same ad and period, never the average of daily frequencies |
| Creative cost per result | ad spend / result of the campaign objective, in the period |
| Tired creative | last 7 days CTR at or below 65% of the previous 7 days CTR, with at least 5,000 impressions in each window |

### Pitfalls

- **Broken thumbnail.** The dashboard showed images that stopped loading within a day, because Meta's URL is reissued at every sync. How to avoid: Copy the image and store ad_id and your own URL.
- **Reels in the static ranking.** Boosted posts came as SHARE with no video_id, and reels entered the image ranking. How to avoid: Format by video delivery over the ad's history.
- **Two ads with the same name.** Grouping by name summed different ads, and the ranking winner was the sum of two. How to avoid: Group by ad_id.
- **Average of averages.** Hook rate stored per day and then averaged gave different numbers by week and by campaign. How to avoid: Store the components and divide at read time.
- **Reach champion.** The single ranking put reach ads with a low CPM at the top, ads that generated no leads at all. How to avoid: Ranking within the campaign objective.
- **Leads counted twice.** The cost per lead ranking added lead to its mirror types, and the best creative had half the real cost per lead. How to avoid: Use the objective result, as in the Meta results case.

Validated on real data: In Nekt's own Meta Ads account, 14 ad names appear on more than one ad_id (376 ads for 362 names), and the creative's video_id is empty on every ad, including video ads. The ranking must aggregate by ad_id and the format must come from video delivery.

### Acceptance test

- [ ] For one client and month, spend and impressions in the creative table equal those in the media table for campaigns that have ad level.
- [ ] A campaign's hook rate and hold rate for the month give the same value computed from the daily table and from the period table.
- [ ] Each ad_id has a single format across its whole history.
- [ ] Every dashboard thumbnail loads 7 days after the load.
- [ ] No ranking compares creatives from different objectives.

### Context document

```text
Creative performance
- The creative key is ad_id. Never group by ad_name.
- Hook rate and hold rate follow the client's definition (below). Always recompute from period sums; do not average rates.
- Meta: video metrics are lists; quartile rate = pXX / video_plays. YouTube: quartile rate over impressions.
- Format: video if the ad had plays on any day; otherwise, the creative's media type.
- Best creative = lowest cost for the campaign objective (conversion: cost per result; reach: CPM; traffic: CPC). Only creatives with spend and at least <n> impressions in the period.
- Fatigue: last 7 days CTR at least 35% below the previous 7, with 5,000 or more impressions in each window, or results falling while frequency and cumulative impressions rise.
- No preview is a source limitation (Meta video and carousel, posts outside the feed, Google). Do not make up ad text.
- Meta results follow the rules of the Meta Ads results by objective case.
Per-client exceptions:
- <client>: hook rate = <video_view / impressions | p25 / impressions>; hold rate = <ThruPlay / video_view | p100 / p25>.
- <client>: ad_name follows <format>_<length>_<script>, separated by _.
- <client>: Meta costs in the creative report <with | without> a <x>% tax.
```

### Variations

- **Creative to sale:** Link the ad_id to the CRM deal and rank by cost per sale.
- **Pillar and script:** Extract the content pillar or script number from the caption or name and compare by group.
- **Paid and organic:** Link the ad to the matching organic post by post ID to compare the same content in both.
