Sponsored Product / Offer (Single Tile)
A sponsored tile placed within product listing grids alongside organic products
Sponsored Product / Offer (Single Tile)
A single sponsored tile placed within a product listing grid alongside organic products. Behavior depends on the redirectToOffers flag from the ad server.
How It Works
Two flows, one format. The ad server response includes a
redirectToOffersflag that determines how the tile behaves:
redirectToOffers === false→ Sponsored Product: Tile shows a product. Click leads to the product page on the shop.redirectToOffers === true→ Sponsored Offers: Tile shows a product with a CTA button linking to the merchant's shop. Click on the button leads to the merchant's shop.Both flows are hybrid — your shop is always responsible for fetching product data and rendering the tile.
| Source | Sponsored Product | Sponsored Offers |
|---|---|---|
| Ad Server | Product ID, click tracking, DSA info | Product ID, offer ID, redirect destination (TBD), click tracking, DSA info |
| Your Shop | Product image, price, name, URL (fetched by product_id) | Product image, price, name, merchant shop name (fetched by product_id + offer_id) |
Key difference: In Sponsored Product, the entire tile links to the product page on your shop. In Sponsored Offers, the tile includes a CTA button linking to the merchant's shop. The exact field carrying the redirect URL in the Offers flow is to be agreed upon with Ring DAS.
Data flow:
Sponsored Product (redirectToOffers === false):
- Your page requests an ad from Ring DAS
- Ad server returns
offers[]withproduct_idandredirectToOffers: false - Your shop fetches product details (image, price, name, URL) from your catalog using
product_id - Your shop renders the tile linking to the product page
- On click, a tracking pixel fires via the click tracking URL
Sponsored Offers (redirectToOffers === true):
- Your page requests an ad from Ring DAS
- Ad server returns
offers[]withproduct_id,offer_id, andredirectToOffers: true - Your shop fetches product details (image, price, name, merchant shop name) from your catalog using
product_id+offer_id - Your shop renders the tile with a CTA button showing the merchant's shop name
- On CTA click, a tracking pixel fires via
offer_urland user is redirected to the merchant's shop
Integration Flow Diagram
sequenceDiagram
participant User as Shop User
participant Page as Shop Page
participant ShopAPI as Shop API/Database
participant DAS as Ring DAS
User->>Page: 1. Request listing page
Page->>DAS: 2. Fetch Ad (fetchNativeAd / bidder)
DAS-->>Page: Response with redirectToOffers flag
alt redirectToOffers === false (Sponsored Product)
Note over Page: 3. Extract product_id from response
Page->>ShopAPI: 4. Fetch product details by product_id
ShopAPI-->>Page: Product image, price, name, URL
Note over Page: 5. Render tile linking to product page
else redirectToOffers === true (Sponsored Offers)
Note over Page: 3. Extract product_id, offer_id from response
Page->>ShopAPI: 4. Fetch product details by product_id + offer_id
ShopAPI-->>Page: Product image, price, name, shop name
Note over Page: 5. Render tile with CTA button
end
Page->>User: 6. Display sponsored tile in product grid
Note over Page: Fire impression tracking
User->>Page: 7. Click tile / CTA button
Page->>DAS: Fire click tracking
Live Demo
See the working implementation:
Frontend Integration (CSR)
Step 1: Load the SDK
Configure the SDK with your Ring DAS credentials. You will receive these values from your Ring DAS account manager.
| Parameter | Description |
|---|---|
target | Site and area identifier (see details below) |
tid | Your Ring DAS tenant ID (format: EA-XXXXXXX) |
dsainfo | Set to 1 to enable DSA (Digital Services Act) transparency info in responses |
Target format:
site/area
- site - Your website identifier assigned by Ring DAS, UPPERCASE (e.g.,
DEMO_PAGE)- area - Page type context where the ad appears, UPPERCASE:
HOMEPAGE,PRODUCT_DETAIL,LISTING,SEARCH- Mobile prefix - For mobile pages, add
M_prefix to site (e.g.,M_DEMO_PAGE/LISTING)Examples:
- Desktop listing:
DEMO_PAGE/LISTING- Mobile search:
M_DEMO_PAGE/SEARCH- Desktop category:
DEMO_PAGE/LISTING
<script>
dlApi = {
target: "DEMO_PAGE/LISTING", // Format: SITE/AREA. For mobile: M_SITE/AREA
tid: 'EA-<<NETWORK_ID>>', // your tenant ID - provided by Ring DAS
dsainfo: 1, // enable DSA transparency info
cmd: [] // command queue
};
</script>
<!-- SDK URL - provided by your Ring DAS account manager -->
<script src="https://<<DAS_CDN>>/<<NETWORK_ID>>/build/dlApi/minit.boot.min.js" async></script>The SDK script URL (
minit.boot.min.js) may differ depending on your integration. Your Ring DAS account manager will provide the correct URL for your setup.
Step 2: Configure Consent
As soon as possible after embedding the script, you should pass consent information. This determines whether personalized ads are served and controls the script's behavior.
dlApi.cmd.push(function (dlApi) {
dlApi.consent({npa: 0}); // 0 = consent given, 1 = no consent
});| Parameter | Value | Description |
|---|---|---|
npa | 0 | Consent given - personalized ads enabled |
npa | 1 | No consent - non-personalized ads only |
Step 3: Set Targeting Context
Set product and category context BEFORE fetching ads. This helps the ad server select the most relevant campaigns for your page content.
| Key-Value | Description |
|---|---|
offer_ids | Product IDs currently displayed on the page |
category_ids | Category IDs for the current page context |
main_category_id | Main product category ID - primary category used for ad matching (product may belong to multiple categories, but has one main) |
dlApi.cmd.push(function (dlApi) {
dlApi.addKeyValue('offer_ids', [10, 20, 1234]); // offers IDs from current page
dlApi.addKeyValue('category_ids', [101, 205]); // category IDs for targeting
dlApi.addKeyValue('main_category_id', 101); // main product category ID
});Why this matters: The ad server uses this context to select campaigns relevant to what the user is viewing, improving click-through rates and campaign performance. See Set Targeting Context for detailed explanation.
Step 4: Fetch the Ad
Use fetchNativeAd() to request a single sponsored tile. The ad server returns offer data and a redirectToOffers flag that determines the tile behavior.
| Parameter | Description |
|---|---|
slot | Fixed value sponsored for this format |
div | Container element ID - required for viewability measurement |
tplCode | Fixed value 1746213/Sponsored-Product for Sponsored Single Tile |
asyncRender | Set to true to manually control when impression is counted |
opts.pos | Slot position (1, 2, ...) when using multiple slots on the same page |
opts.offers_limit | Optional. Maximum number of offers returned by the ad server. Request more than one to increase the chance of matching a product in your catalog |
Multiple offers for higher fill rate: When
offers_limitis greater than 1, the ad server may return multiple offers. Your shop should iterate through the offers and display the first one that matches a product in your catalog. This increases fill rate when some advertised products are not available in your shop.
dlApi.cmd.push(function (dlApi) {
dlApi.fetchNativeAd({
slot: 'sponsored', // fixed value for Sponsored Single Tile
div: 'ad-sponsored', // container ID for viewability tracking
opts: {
pos: 1, // slot position (1, 2, ... for multiple slots)
offers_limit: 3 // request up to 3 offers to increase fill rate
},
tplCode: '1746213/Sponsored-Product', // fixed value for Sponsored Single Tile
asyncRender: true, // manually count impression with ad.render()
}).then(async function (ad) {
if (!ad) {
console.log('[Sponsored Tile] No ad available');
return;
}
console.log('[Sponsored Tile] Ad response:', ad);
// 1. Extract offers from the feed
var offers = (ad.fields?.feed?.offers) || [];
if (offers.length === 0) {
console.log('[Sponsored Tile] No offers in response');
return;
}
var dsa = ad.dsa || null;
var redirectToOffers = ad.fields?.feed?.redirectToOffers;
// 2. Find the first offer available in your shop catalog
var offer = null;
var shopProduct = null;
for (var i = 0; i < offers.length; i++) {
var candidate = offers[i];
var product = redirectToOffers
? await fetchProductDetailsFromShop(candidate.product_id, candidate.offer_id)
: await fetchProductDetailsFromShop(candidate.product_id);
if (product) {
offer = candidate;
shopProduct = product;
break;
}
}
if (!offer) {
console.log('[Sponsored Tile] No matching product found in shop catalog');
return;
}
// 3. Determine the flow based on redirectToOffers flag
if (redirectToOffers === false) {
// SPONSORED PRODUCT: tile links to product page on your shop
renderSponsoredProduct(offer, shopProduct, dsa);
} else {
// SPONSORED OFFERS: tile with CTA button linking to merchant's shop
renderSponsoredOffer(offer, shopProduct, dsa);
}
// 4. Count impression after rendering
ad.render();
}).catch(function (err) {
console.error('[Sponsored Tile] Ad could not be loaded:', err);
});
// Trigger ad fetch
dlApi.fetch();
});Step 5: Handle Response (Dual Flow)
In both flows, your shop fetches product details from your catalog by product_id. The redirectToOffers flag determines how the tile behaves:
- Check
ad.fields.feed.redirectToOffersfrom the response - If
false→ Sponsored Product: tile links to the product page on your shop - If
true→ Sponsored Offers: tile shows a CTA button with the merchant shop name linking to the merchant's shop
Step 6: Render the Tile
Sponsored Product (redirectToOffers === false): The entire tile links to the product page on your shop. Click tracking fires via a tracking pixel on click.
/**
* Render Sponsored Product tile - links to product page on your shop
*/
function renderSponsoredProduct(offer, shopProduct, dsa) {
var container = document.getElementById('ad-sponsored');
// Use product data from your shop catalog
var productUrl = shopProduct.url;
var productImage = shopProduct.image;
var productName = shopProduct.name;
var productPrice = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(shopProduct.price);
// Build DSA transparency info
var dsaHtml = '';
if (dsa) {
dsaHtml = '<div class="dsa-info">'
+ '<span class="dsa-icon" title="Ad transparency">i</span>'
+ '<div class="dsa-tooltip">'
+ '<div>Advertiser: ' + escapeHtml(dsa.behalf || 'N/A') + '</div>'
+ '<div>Paid by: ' + escapeHtml(dsa.paid || 'N/A') + '</div>'
+ '</div></div>';
}
container.innerHTML = ''
+ dsaHtml
+ '<span class="sponsored-badge">Sponsored Product</span>'
+ '<a href="' + escapeHtml(productUrl) + '" target="_blank" rel="noopener nofollow sponsored"'
+ ' onclick="new Image().src=\'' + escapeHtml(offer.offer_url || '') + '\';">'
+ '<img src="' + escapeHtml(productImage) + '" alt="' + escapeHtml(productName) + '">'
+ '<div class="product-name">' + escapeHtml(productName) + '</div>'
+ '<div class="product-price">' + productPrice + '</div>'
+ '</a>';
}Sponsored Offers (redirectToOffers === true): The tile shows product data from your shop catalog with a CTA button. The CTA button displays the merchant's shop name and links to the merchant's shop.
/**
* Render Sponsored Offer tile - CTA button links to merchant's shop
* Product data (image, name, price) comes from your shop catalog
*/
function renderSponsoredOffer(offer, shopProduct, dsa) {
var container = document.getElementById('ad-sponsored');
// Get shop name from your shop catalog (fetched alongside product data)
var shopName = shopProduct.shopName || 'Shop';
// Use product data from your shop catalog
var productImage = shopProduct.image;
var productName = shopProduct.name;
var productPrice = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(shopProduct.price);
// Build DSA transparency info
var dsaHtml = '';
if (dsa) {
dsaHtml = '<div class="dsa-info">'
+ '<span class="dsa-icon" title="Ad transparency">i</span>'
+ '<div class="dsa-tooltip">'
+ '<div>Advertiser: ' + escapeHtml(dsa.behalf || 'N/A') + '</div>'
+ '<div>Paid by: ' + escapeHtml(dsa.paid || 'N/A') + '</div>'
+ '</div></div>';
}
// Note: The exact redirect URL field for the CTA destination
// is to be agreed upon with Ring DAS. Currently uses offer.offer_url.
container.innerHTML = ''
+ dsaHtml
+ '<span class="sponsored-badge">Sponsored Offer</span>'
+ '<img src="' + escapeHtml(productImage) + '" alt="' + escapeHtml(productName) + '">'
+ '<div class="product-name">' + escapeHtml(productName) + '</div>'
+ '<div class="product-price">' + productPrice + '</div>'
+ '<a class="cta-button" href="' + escapeHtml(offer.offer_url) + '" target="_blank" rel="noopener nofollow sponsored"'
+ ' onclick="new Image().src=\'' + escapeHtml(offer.offer_url) + '\';">'
+ escapeHtml(shopName) + '</a>';
}
// Helper function to escape HTML special characters
function escapeHtml(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}Implementation note: The
ad.render()call after rendering counts the impression. Always call it after the tile is visible in the DOM. Both flows fireoffer.offer_urlas a tracking pixel on click. In Sponsored Product the tile links to the product page on your shop; in Sponsored Offers the CTA button links to the merchant's shop (exact destination field TBD).
Backend Integration (SSR)
For server-side rendering, fetch ad data from the bidder, then handle the dual flow before rendering the page.
Required: The SDK from Step 1 (Frontend) must still be loaded on the page for viewability tracking via
registerBidResponse().
Step 1: Fetch Ad from Bidder
Make a GET request to the Ring DAS bidder endpoint. The bidder URL will be provided by your Ring DAS account manager.
Request Configuration
| Parameter | Description |
|---|---|
BIDDER_URL | Bidder endpoint URL - provided by your Ring DAS account manager |
NETWORK_ID | Your Ring DAS network ID (e.g., 1746213) |
tmax | Request timeout in milliseconds |
ext.src | Optional. Set to s2s for server-to-server requests |
Placement Parameters
These parameters define WHERE the ad appears and WHICH format to serve.
| Parameter | Description |
|---|---|
site.id | Your site identifier in Ring DAS |
site.ext.area | Page type context, UPPERCASE: HOMEPAGE, PRODUCT_DETAIL, LISTING, SEARCH |
imp[].tagid | Fixed value sponsored for this format |
Targeting Parameters
| Parameter | Description |
|---|---|
imp[].ext.offerids | List of product offer IDs from current page for contextual targeting and relevancy (optional) |
imp[].ext.categoryids | Product category IDs for contextual targeting and relevancy (optional) |
Consent & Privacy Parameters
| Parameter | Description |
|---|---|
user.ext.npa | Consent flag: false = consent given, true = no consent |
regs.gdpr | GDPR applies flag: 1 = GDPR applies, 0 = does not apply |
regs.gpp | GPP consent string (optional) - TCF-compliant consent string from your CMP |
regs.ext.dsa | Set to 1 to request DSA transparency info in response |
Ad Behavior Parameters
| Parameter | Description |
|---|---|
imp[].ext.no_redirect | Set to true if adclick URLs should only fire tracking pixel without redirect (shop handles redirect via href) |
imp[].ext.offers_limit | Optional. Maximum number of offers returned by the ad server. Request more than one to increase the chance of matching a product in your catalog |
Consent handling: You can pass consent via
user.ext.npa(simple flag) OR viaregs.gpp(TCF-compliant string). If you have a TCF-compliant CMP, use the GPP string for more granular consent information.
User Identifier
Important for backend integration: Pass a user identifier consistently per user. This enables frequency capping, personalization, and other targeting features. Without an identifier, each request is treated as a new user.
| Parameter | Description |
|---|---|
user.ext.ids.sid | Custom user/session identifier provided by the integrating party. Can be any persistent, unique value your system already maintains (e.g., internal user ID, session token, or device identifier). Free-form string. |
user.ext.ids.lu | Local user ID from the ea_uuid cookie on your shop's domain (set by Ring DAS). Must follow the alphanumeric format (e.g., 202408221036415499301131). |
Choosing an identifier:
sid— A free-form string you provide. Pass your own session ID, internal user ID, or any other persistent identifier. The integrating party is responsible for ensuring persistence and consistency per user.lu— The value from theea_uuidcookie set by Ring DAS on the user's browser. Must follow the strict alphanumeric format.You can pass both identifiers simultaneously. At least one is recommended.
For more details on user identifiers, see API Parameters Reference.
// Node.js backend
const NETWORK_ID = '${NETWORK_ID}';
const BIDDER_URL = `https://das.idealo.com/${NETWORK_ID}/bid`;
const requestBody = {
id: Math.random().toString(16).substring(2, 15),
imp: [{
id: 'imp-1',
tagid: 'sponsored', // fixed value for Sponsored Single Tile
secure: 1,
native: { request: "{}" },
ext: {
no_redirect: true, // adclick URLs fire tracking only, no redirect
offers_limit: 3, // request up to 3 offers to increase fill rate
offerids: ['12345', '67890'], // optional: product IDs from current page
categoryids: ['101', '205'] // optional: category IDs for targeting
}
}],
site: {
id: 'DEMO_PAGE', // your site ID (UPPERCASE)
ext: {
area: 'LISTING' // page type (UPPERCASE): HOMEPAGE, PRODUCT_DETAIL, LISTING, SEARCH
}
},
user: {
ext: {
npa: false, // false = consent given, true = no consent
ids: {
lu: '202408221036415499301131', // from ea_uuid cookie
sid: 'your-persistent-user-or-session-id' // your own identifier (alternative to lu)
}
}
},
ext: {
network: NETWORK_ID,
keyvalues: {}, // optional targeting parameters
is_non_prebid_request: true,
src: 's2s' // optional: indicates server-to-server request
},
regs: {
gdpr: 1, // 1 = GDPR applies, 0 = does not apply
gpp: 'YOUR_GPP_STRING', // optional: TCF-compliant consent string from CMP
ext: {
dsa: 1 // request DSA transparency info
}
},
tmax: 1000 // request timeout in milliseconds
};
// Send as GET request with body in data= parameter
const encodedData = encodeURIComponent(JSON.stringify(requestBody));
const response = await fetch(`${BIDDER_URL}?data=${encodedData}`);
// Check for no ad available (204 No Content)
if (response.status === 204) {
console.log('No ad available');
return null;
}
const bidResponse = await response.json();No ad available: When no ad matches the request criteria, the server returns HTTP
204 No Contentwith an empty response body. Always check the status code before parsing JSON.
Step 2: Parse Response and Handle Dual Flow
Extract the offer data and redirectToOffers flag, then handle each flow:
const bid = bidResponse?.seatbid?.[0]?.bid?.[0];
if (!bid) {
return null; // No bid in response
}
// Parse adm (it's a JSON string)
const adm = JSON.parse(bid.adm);
// 1. Extract offer data and redirectToOffers flag
const offers = adm.fields?.feed?.offers || [];
const redirectToOffers = adm.fields?.feed?.redirectToOffers;
if (offers.length === 0) {
return null; // No offers in response
}
// 2. Extract DSA transparency info
const dsaInfo = bid.ext?.dsa ? {
advertiser: bid.ext.dsa.behalf,
payer: bid.ext.dsa.paid
} : null;
// 3. Find the first offer available in your shop catalog
let offer = null;
let shopData = null;
for (const candidate of offers) {
const product = redirectToOffers
? await fetchProductFromDatabase(candidate.product_id, candidate.offer_id)
: await fetchProductFromDatabase(candidate.product_id);
if (product) {
offer = candidate;
shopData = product;
break;
}
}
if (!offer) {
console.log('No matching product found in shop catalog');
return null;
}
// 4. Determine the flow based on redirectToOffers flag
if (redirectToOffers === false) {
console.log('Sponsored Product flow - product:', offer.product_id);
} else {
console.log('Sponsored Offers flow - product:', offer.product_id);
}
// 5. Prepare data for rendering
const adData = {
offer,
redirectToOffers,
shopData, // product data from your shop catalog
dsaInfo,
rawResponse: bidResponse,
containerId: 'ad-sponsored'
};Step 3: Render HTML + Register Response
Render the tile based on the flow, then register the bid response for viewability tracking:
<div id="ad-sponsored">
<% if (adData.redirectToOffers === false) { %>
<!-- SPONSORED PRODUCT: tile links to product page on your shop -->
<div class="tile">
<% if (adData.dsaInfo) { %>
<div class="dsa-info">
<span>Advertiser: <%= adData.dsaInfo.advertiser %></span>
<span>Paid by: <%= adData.dsaInfo.payer %></span>
</div>
<% } %>
<span class="sponsored-badge">Sponsored Product</span>
<a href="<%= adData.shopData.url %>"
target="_blank" rel="noopener nofollow sponsored"
onclick="new Image().src='<%= adData.offer.offer_url %>';">
<img src="<%= adData.shopData.image %>" alt="<%= adData.shopData.name %>">
<div class="product-name"><%= adData.shopData.name %></div>
<div class="product-price">
<%= new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(adData.shopData.price) %>
</div>
</a>
</div>
<% } else { %>
<!-- SPONSORED OFFERS: product data from shop catalog + CTA button links to merchant's shop -->
<div class="tile">
<% if (adData.dsaInfo) { %>
<div class="dsa-info">
<span>Advertiser: <%= adData.dsaInfo.advertiser %></span>
<span>Paid by: <%= adData.dsaInfo.payer %></span>
</div>
<% } %>
<span class="sponsored-badge">Sponsored Offer</span>
<img src="<%= adData.shopData.image %>" alt="<%= adData.shopData.name %>">
<div class="product-name"><%= adData.shopData.name %></div>
<div class="product-price">
<%= new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(adData.shopData.price) %>
</div>
<!-- Note: The redirect destination href field is TBD — to be agreed upon with Ring DAS -->
<a class="cta-button" href="<%= adData.offer.offer_url %>"
target="_blank" rel="noopener nofollow sponsored"
onclick="new Image().src='<%= adData.offer.offer_url %>';">
<%= adData.shopData.shopName || 'Shop' %>
</a>
</div>
<% } %>
</div>
<!-- Register for viewability and impression tracking -->
<script>
dlApi.cmd.push(function () {
dlApi.registerBidResponse(<%- JSON.stringify(adData.rawResponse) %>, 'ad-sponsored');
});
</script>
registerBidResponse()counts impressions. Only call it when the ad is actually rendered and visible to the user. If your shop decides not to display the ad (e.g., product not in catalog, business logic prevents display), do NOT callregisterBidResponse()— calling it without a visible ad counts a false impression.
What the Ad Server Returns
| Field | Description |
|---|---|
ad.fields.feed.redirectToOffers | Determines the flow: false = Sponsored Product, true = Sponsored Offers |
ad.fields.feed.offers | Array of offer objects |
ad.fields.feed.offers[].product_id | Product ID — use to fetch product details from your shop catalog |
ad.fields.feed.offers[].offer_id | Offer ID — used with product_id to fetch product details in the Sponsored Offers flow |
ad.fields.feed.offers[].offer_url | Click tracking URL — fire as pixel on click |
ad.dsa.behalf | Advertiser name (DSA compliance, requires dsainfo: 1) |
ad.dsa.paid | Entity that paid for the ad (DSA compliance) |
ad.meta.adclick | Click tracking base URL — available in response but not used in these examples |
Sponsored Offers redirect destination: The exact field carrying the redirect URL for the CTA button in the Sponsored Offers flow is to be agreed upon with Ring DAS. Currently,
offer_urlis used for click tracking and may serve as the redirect destination. Confirm the field mapping with your Ring DAS account manager.
Testing Your Integration
Implement this to enable integration testing without running production campaigns. Pass URL parameters from the page URL to your backend request.
Backend (Bidder API):
// Extract test parameters from page URL
const pageUrl = 'https://your-shop.com/?test_kwrd=test+keywords#adbeta=l1234567!slot.sponsored';
const url = new URL(pageUrl);
const adbeta = url.hash.match(/adbeta=([^&]+)/)?.[1];
const testKwrd = url.searchParams.get('test_kwrd');
const requestBody = {
// ... standard request fields ...
site: {
id: 'DEMO_PAGE',
ext: {
area: 'LISTING',
...(testKwrd && { kwrd: testKwrd }) // if test_kwrd in URL, add to kwrd
}
},
ext: {
network: NETWORK_ID,
keyvalues: {},
is_non_prebid_request: true,
...(adbeta && { adbeta }) // if adbeta in URL, add it
},
// ...
};Caching Considerations
Important: Improper caching can significantly reduce ad revenue and break tracking.
Do NOT cache:
- Tracking pixels - Caching impression/click pixels prevents accurate counting and drastically reduces revenue
- API responses - Caching bid responses prevents real-time optimization, budget pacing, and frequency capping
Safe to cache:
- Static assets (CSS, JS, images from your shop)
- Product data from your own database
Multiple Ads on Single Page
To display multiple sponsored tiles on a single page (e.g., positions 1 and 4 in a product grid), include multiple impressions in one request with unique IDs and positions.
Backend (Bidder API):
const requestBody = {
id: 'request-123',
imp: [
{
id: 'ad-sponsored-1', // unique ID for first slot
tagid: 'sponsored',
secure: 1,
native: { request: "{}" },
ext: { pos: 1 } // position in the grid
},
{
id: 'ad-sponsored-2', // unique ID for second slot
tagid: 'sponsored',
secure: 1,
native: { request: "{}" },
ext: { pos: 2 } // position in the grid
}
],
// ... rest of request unchanged
};Response handling:
Each impression may return a separate bid. Match bids to impressions using the impid field:
const bids = bidResponse?.seatbid?.[0]?.bid || [];
bids.forEach(bid => {
const impressionId = bid.impid; // matches imp[].id from request
// Parse each bid independently - each may have different redirectToOffers value
const adm = JSON.parse(bid.adm);
const redirectToOffers = adm.fields?.feed?.redirectToOffers;
// Handle each slot's dual flow independently
});Per-slot
registerBidResponse()rules:
- Each slot resolves independently — one may be a Sponsored Product while another is a Sponsored Offer.
- Call per element: Each slot requires a separate
registerBidResponse()call with its own container element ID.- Filter bids per slot: Pass a response containing ONLY the bid displayed in that container — filter the
seatbid[].bidarray to include only the matching bid. Do not pass the entire response with all bids.- Call only when displayed: Only call
registerBidResponse()after the ad is actually rendered. If no matching product is found and the slot stays empty, skip the call — it counts impressions.
Frontend (CSR):
For frontend integration, make separate fetchNativeAd() calls with different opts.pos values:
// Slot 1 (position 1 in the grid)
dlApi.fetchNativeAd({
slot: 'sponsored',
div: 'ad-sponsored-1',
opts: { pos: 1 },
tplCode: '1746213/Sponsored-Product',
asyncRender: true
}).then(async function (ad) {
// Handle ad response for slot 1
});
// Slot 2 (position 4 in the grid)
dlApi.fetchNativeAd({
slot: 'sponsored',
div: 'ad-sponsored-2',
opts: { pos: 2 },
tplCode: '1746213/Sponsored-Product',
asyncRender: true
}).then(async function (ad) {
// Handle ad response for slot 2
});
dlApi.fetch();Related
- Format Overview - Compare available ad formats
- Branded Products - Homepage banner with brand logo and curated products
- Brand Store (Web) - Button format for product detail pages
Updated about 11 hours ago
