Skip to main content

Terrific and Programmatic Ads

Understand how programmatic ads are integrated and executed within the Terrific Carousel.

Updated this week

What Terrific Does and What It Does Not Do

Before diving into technical details, it is important to clarify Terrific’s role in the monetization chain.

What Terrific Does

  1. Executes the tag configured by the client (GPT or VAST).

  2. Properly renders the Ad Server response.

  3. Supports both Display (GPT) and Video (VAST) ads.

  4. Allows Web-compatible integrations within the Timeline.

  5. Respects targeting, priorities, and rules defined in the client’s Ad Manager.

What Terrific Does Not Do

  1. Does not manage advertising campaigns.

  2. Does not configure line items.

  3. Does not control priorities or targeting.

  4. Does not participate in the programmatic auction.

  5. Cannot guarantee fill rate.

  6. Does not decide which creative is displayed.

All monetization logic lives inside the client’s Google Ad Manager.

Recommended Formats

For a vertical 9:16 environment:

Display (GPT)

  • 320x480

  • 300x600

  • 300x250 (fallback)

  • 1x1 (tracking / backup)

Recommendation: Prioritize 320x480 and 300x600 to maintain visual consistency.

Video (VAST)

  • 360x640

  • 720x1280

  • 1080x1920

Strategic recommendation: Always validate that inventory is configured for vertical formats.

What Happens When There is no Fill?

When no eligible campaign exists:

  1. The Ad Server returns an empty response.

  2. The slot remains without content.

  3. The Timeline card may appear empty.

Recommendations to Maximize Fill Rate

Fill rate depends entirely on inventory configuration within Google Ad Manager. However, there are best practices that help ensure the Timeline consistently displays advertising content.

Always Configure House Ads as Fallback

Primary recommendation: Every Ad Unit should include a House line item as fallback.

Why? If there is no:

  1. Active Sponsorship

  2. Direct campaign

  3. Programmatic demand

  4. Eligible Open Auction bid

The slot will remain empty. A House Ad ensures the placement never stays without content.

Allow Multiple Compatible Sizes

In GPT, instead of defining a single size: [320, 480]

It is better to allow: [[320,480], [300,600], [300,250]]

This increases auction eligibility and improves fill opportunities.

Avoid Overly Restrictive Targeting

Ensure there are no excessive filters applied to:

  1. Geolocation

  2. Device

  3. Domain

  4. Audience

  5. Schedule

Pay special attention when the Timeline runs inside an iframe.

Properly Authorize Domains

If the domain where the Timeline runs is not authorized in Google Ad Manager, no-fill may occur even if the campaign is active.

Understand That Fill Is Dynamic

Fill rate may vary depending on:

  1. Time of day

  2. Available budget

  3. Auction competition

  4. User profile

A placement may show an ad at one moment and not at another.

How It's Managed in the Terrific Admin Panel

Each carousel can have its own Google Ads integration. The configuration can be done through the Ad Integration option within the carousel settings.

2026-03-04_20-57-52-20260305-030110.png

Ads will only be displayed once the user starts navigating the full-screen version (vertical scroll) within the carousel.

To configure Ads in this section, the first step is to enable the functionality by turning on the toggle shown as (1) in the image. After that, the following options can be configured:

2. Skip Ad: Allows enabling the Ad skip option. When enabled, a skip button will appear after 3 seconds. If disabled, users will not be able to skip the Ad until it finishes playing.

3. Ad Frequency: Defines how often an Ad will appear between cards/content.
For example, if set to 5, the carousel will display 4 content cards and the 5th element will be an Ad.

4. Ad Start Point: Defines from which card/content Ads will start appearing.
This helps create a more personalized UX. For example, Ads can be configured to start from asset 10, and from that point the frequency defined in the previous step will apply.

5. Audience Limit: Allows setting a maximum number of users who will see Ads. If left empty, Ads will be shown to all users.

6. Device Targeting: Allows configuring whether Ads should appear on Mobile, Desktop, or both.

7. Ad Script: This is where the Ad Unit script provided by the client should be added, either VAST or GPT.

8. Confirmation: It is mandatory to confirm that the nature of the integration is understood.

9. Preview: You can preview how the Ads will appear.
Due to domain restrictions, in some cases the content may not appear in the preview, but it will work correctly once the integration is live on the production site.

10. Save: Save the configuration and you're done.

2026-03-04_21-02-28-20260305-030824.png

Integration Script Templates

Below you will find two templates that can be used as a base for the Ads integration.

GPT

When using the following template, you only need to replace lines 20 to 29 with the script provided by the client.

<html style="overflow:hidden"> 
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>UnoTV Ads</title>
<script async src="https://securepubads.g.doubleclick.net/tag/js/gpt.js" crossorigin="anonymous"></script>
<style>
.ad-wrap {
max-width: 970px;
margin: 24px auto; text-align: center;
}
#div-gpt-ad-1761930849217-0 {
width: 100%;
}
</style>
</head>
<body>
<div class="ad-wrap">
<div id="div-gpt-ad-1761930849217-0">
<script>
window.googletag = window.googletag || {cmd: []};
googletag.cmd.push(function()
{ // Definir slot con múltiples tamaños
googletag.defineSlot('/121173452/UnoTV/noticias/inicio/terrificdisplay',[[300, 600], [320, 480]], 'div-gpt-ad-1761930849217-0').addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.pubads().collapseEmptyDivs();
googletag.enableServices();
googletag.display('div-gpt-ad-1761930849217-0'); });
</script>
</div>
</div>
</body>
</html>

VAST

When using the following template, you only need to replace the URL on line 35 with the URL provided by the client.

<html lang="es" style="overflow:hidden">
<head>
<meta charset="utf-8" />
<title>VAST</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
.video-wrap { position: relative; width: 100%; max-width: 360px; margin: 0 auto; }
.video-9x16 { position: relative; width: 100%; aspect-ratio: 9 / 16; background: #000; overflow: hidden; }
#ad-container { position: absolute; inset: 0; }
#player video,
#player iframe,
#player div[style*="absolute"] {
object-fit: contain !important;
width: 100% !important;
height: 100% !important;
max-width: 100% !important;
max-height: 100% !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
}
</style>
<script src="https://imasdk.googleapis.com/js/sdkloader/ima3.js"></script>
</head>
<body>
<div class="video-wrap">
<div class="video-9x16" id="player">
<video id="content" playsinline webkit-playsinline muted></video>
<div id="ad-container"></div>
</div>
</div>
<script>
const adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?iu=/205320464/RCN_RADIO/EL_SOL/RCN_RADIO_EL_SOL_TERRIFIC&description_url=www.elsol.com.co&tfcd=0&npa=0&sz=1x1%7C300x600%7C320x480&gdfp_req=1&unviewed_position_start=1&output=vast&env=vp&impl=s&correlator=256&plcmt=1&vpmute=1&vpa=auto";
const contentVideo = document.getElementById('content');
const adContainer = document.getElementById('ad-container');
let adDisplayContainer, adsLoader, adsManager;
let initialized = false, requested = false;
function initIMA() {
if (initialized) return;
initialized = true;
adDisplayContainer = new google.ima.AdDisplayContainer(adContainer, contentVideo);
adsLoader = new google.ima.AdsLoader(adDisplayContainer);
adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded);
adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError);
}
function requestAds() {
if (requested) return;
requested = true;
const req = new google.ima.AdsRequest();
req.adTagUrl = adTagUrl;
req.setAdWillAutoPlay(true);
req.setAdWillPlayMuted(true);
adsLoader.requestAds(req);
}
function onAdsManagerLoaded(e) {
adsManager = e.getAdsManager(contentVideo);
adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, () => {
try { adsManager.start(); } catch (err) { console.warn('[IMA] Start error:', err); }
});
adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, () => {
console.log('[IMA] Todos los anuncios completados');
});
adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError);
const rect = document.getElementById('player').getBoundingClientRect();
adsManager.init(rect.width, rect.height, google.ima.ViewMode.NORMAL);
}
function onAdError(ev) {
const err = ev && ev.getError ? ev.getError() : ev;
console.warn('[IMA] AdError:', err);
if (adsManager) { try { adsManager.destroy(); } catch(_){} }
}
function tryAutoplay() {
contentVideo.muted = true;
contentVideo.playsInline = true;
try {
initIMA();
adDisplayContainer.initialize();
requestAds();
} catch (e) {
bindFirstUserGesture();
}
}
function bindFirstUserGesture() {
const once = () => {
document.removeEventListener('pointerdown', once);
document.removeEventListener('touchstart', once);
document.removeEventListener('keydown', once);
initIMA();
try { adDisplayContainer.initialize(); } catch(_) {}
requestAds();
};
document.addEventListener('pointerdown', once, { passive: true });
document.addEventListener('touchstart', once, { passive: true });
document.addEventListener('keydown', once);
console.log('[IMA] Esperando interacción del usuario para iniciar el anuncio…');
}
window.addEventListener('resize', () => {
if (!adsManager) return;
const rect = document.getElementById('player').getBoundingClientRect();
adsManager.resize(rect.width, rect.height, google.ima.ViewMode.NORMAL);
});
window.addEventListener('DOMContentLoaded', tryAutoplay);
</script>
</body>
</html>

Did this answer your question?