Branded Products

A homepage display banner showing brand logo and curated products

Branded Products

A homepage display banner showing the brand logo and a curated set of products selected by the brand.

How It Works

Important: This is a hybrid format. The ad server provides the brand logo and a list of product IDs. Your shop must fetch product details (image, price, name, URL) for each product ID from your own system.

SourceData
Ad ServerBrand logo (bannerImage), list of product IDs (offers[].product_id), click tracking, DSA info
Your ShopProduct details for each ID: image, price, name, URL

Data flow:

  1. Ad server returns bannerImage (brand logo) and offers[] array with product_id for each product
  2. Your shop extracts each product_id from the offers
  3. Your shop fetches product details (image, price, name, URL) from your product database
  4. Your shop renders the banner with combined data

Number of products is configurable. The examples in this documentation show 5 products, but your shop decides how many to display (e.g., 2, 5, 7, or more). Simply adjust the limit in your rendering code.

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.

ParameterDescription
targetYour site identifier in Ring DAS
tidYour Ring DAS tenant ID (format: EA-XXXXXXX)
dsainfoSet to 1 to enable DSA (Digital Services Act) transparency info in responses
<script>
    dlApi = {
        target: "demo_page",      // your site ID - provided by Ring DAS
        tid: 'EA-1746213',        // 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.idealo.com/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
});
ParameterValueDescription
npa0Consent given - personalized ads enabled
npa1No consent - non-personalized ads only

Step 3: Fetch the Ad

Use fetchNativeAd() to fetch the ad. The ad server returns the brand logo and product IDs - you must fetch product details from your shop.

ParameterDescription
slotFixed value branded_products for this format
divContainer element ID - required for viewability measurement
tplCodeFixed value 1746213/Sponsored-Product-Plus for Branded Products
asyncRenderSet to true to manually control when impression is counted
offers_limitMaximum number of products returned from ad server
dlApi.cmd.push(function (dlApi) {
    // Fetch ad using fetchNativeAd
    dlApi.fetchNativeAd({
        slot: 'branded_products',                   // fixed value for Branded Products
        div: 'branded-products-container',          // container ID for viewability tracking
        opts: {
            offers_limit: 5                         // maximum number of products returned from ad server
        },
        tplCode: '1746213/Sponsored-Product-Plus',  // fixed value for Branded Products
        asyncRender: true,                          // manually count impression with ad.render()
    }).then(async function (ad) {
        if (!ad) {
            console.log('[Branded Products] No ad available');
            return;
        }

        console.log('[Branded Products] Ad response:', ad);

        // 1. Extract data from ad server
        const bannerImage = ad.fields?.bannerImage || '';      // Brand logo URL
        const offers = ad.fields?.feed?.offers || [];
        const dsaInfo = ad.dsa;

        // 2. Extract product IDs from offers
        const productIds = offers.map(offer => offer.product_id);
        console.log('[Branded Products] Product IDs to fetch:', productIds);

        // 3. Fetch product details from YOUR shop (implement this function)
        const productDetails = await fetchProductDetailsFromShop(productIds);

        // 4. Combine shop data with ad server tracking URLs
        // Construct click tracking URLs (base URL + offer parameter)
        // If meta.adclick is empty, use offer_url directly (without encoding)
        const productsWithTracking = productDetails.map((product, index) => ({
            ...product,
            trackingUrl: ad.meta.adclick
                ? ad.meta.adclick + encodeURIComponent(offers[index]?.offer_url || '')
                : (offers[index]?.offer_url || '')
        }));

        // 5. Render the ad with combined data
        renderBrandedProducts(bannerImage, productsWithTracking, dsaInfo);

        // 6. Count impression after rendering
        ad.render();
    }).catch(function (err) {
        console.error('[Branded Products] Ad could not be loaded:', err);
    });

    // Trigger ad fetch
    dlApi.fetch();
});

Step 4: Fetch Product Details from Your Shop

Implement a function to fetch product details based on product IDs. This connects to YOUR product database/API.

/**
 * Fetch product details from your shop's product API
 * @param {string[]} productIds - Array of product IDs from ad server
 * @returns {Promise<Array>} - Product details with image, price, name, URL
 */
async function fetchProductDetailsFromShop(productIds) {
    // Example: Call your shop's product API
    const products = await Promise.all(
        productIds.map(async (productId) => {
            // Replace with your actual product API endpoint
            const response = await fetch(`/api/products/${productId}`);
            const product = await response.json();

            return {
                product_id: productId,
                image: product.imageUrl,
                name: product.title,
                price: product.price,
                url: product.detailPageUrl
            };
        })
    );

    return products.filter(p => p !== null);  // Filter out any failed fetches
}

Implementation note: The onclick handler shown below is an example. Your shop is responsible for implementing click tracking - simply fire a tracking pixel on product click using the constructed tracking URL.

Example code: The rendering function below is provided as a documentation example. Adapt it to your shop's templating system and styling requirements.

Step 5: Render the Ad

Render the brand logo and product tiles with the combined data:

function renderBrandedProducts(bannerImage, products, dsaInfo) {
    const container = document.getElementById('branded-products-container');
    const MAX_PRODUCTS = 5;

    // Build products HTML using shop data + ad server tracking URLs
    let productsHtml = '';
    products.slice(0, MAX_PRODUCTS).forEach(function(product) {
        const price = new Intl.NumberFormat('de-DE', {
            style: 'currency',
            currency: 'EUR'
        }).format(product.price);

        productsHtml += `
            <a href="${product.url}" class="product-tile"
               target="_blank" rel="noopener nofollow sponsored"
               onclick="new Image().src='${product.trackingUrl}';">
                <img src="${product.image}" alt="${escapeHtml(product.name)}">
                <div class="product-name">${escapeHtml(product.name)}</div>
                <div class="product-price">${price}</div>
            </a>
        `;
    });

    // Build DSA info HTML (Digital Services Act transparency)
    let dsaHtml = '';
    if (dsaInfo) {
        dsaHtml = `
            <div class="dsa-info">
                <span class="dsa-icon" title="Ad transparency">i</span>
                <div class="dsa-tooltip">
                    <div>Advertiser: ${dsaInfo.behalf || 'N/A'}</div>
                    <div>Paid by: ${dsaInfo.paid || 'N/A'}</div>
                </div>
            </div>
        `;
    }

    // Render complete format
    // bannerImage = from ad server
    // products = from YOUR shop + trackingUrl from ad server
    container.innerHTML = `
        <div class="sponsored-header">
            <span class="sponsored-badge">Sponsored</span>
            ${dsaHtml}
        </div>
        <div class="brand-section">
            <img class="brand-logo" src="${bannerImage}" alt="Brand Logo">
        </div>
        <div class="products-section">
            ${productsHtml}
        </div>
    `;
}

// Helper function to escape HTML special characters
function escapeHtml(text) {
    if (!text) return '';
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

Backend Integration (SSR)

For server-side rendering, fetch ad data from the bidder, then fetch product details from your shop 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.

ParameterDescription
BIDDER_URLBidder endpoint URL - provided by your Ring DAS account manager
NETWORK_IDYour Ring DAS network ID (e.g., 1746213 for Branded Products)
site.idYour site identifier in Ring DAS
imp[].tagidFixed value branded_products for this format
user.ext.npaConsent flag: false = consent given, true = no consent
regs.gdprGDPR applies flag: 1 = GDPR applies, 0 = does not apply
regs.gppGPP consent string (optional) - TCF-compliant consent string from your CMP
regs.ext.dsaSet to 1 to request DSA transparency info in response
imp[].ext.no_redirectSet to true if adclick URLs should only fire tracking pixel without redirect (shop handles redirect via href)
tmaxRequest timeout in milliseconds

Consent handling: You can pass consent via user.ext.npa (simple flag) OR via regs.gpp (TCF-compliant string). If you have a TCF-compliant CMP, use the GPP string for more granular consent information.

// Node.js backend
const NETWORK_ID = '1746213';  // Branded Products 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: 'branded_products',                  // fixed value for Branded Products
        secure: 1,
        native: { request: "{}" },
        ext: {
            offers_limit: 5,                       // maximum number of products returned from ad server
            no_redirect: true                      // adclick URLs fire tracking only, no redirect
        }
    }],
    site: { id: 'demo_page' },                      // your site ID
    user: {
        ext: {
            npa: false                              // false = consent given, true = no consent
        }
    },
    ext: {
        network: NETWORK_ID,
        keyvalues: {},                              // optional targeting parameters
        is_non_prebid_request: true
    },
    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');
}

const bidResponse = await response.json();

No ad available: When no ad matches the request criteria, the server returns HTTP 204 No Content with an empty response body. Always check the status code before parsing JSON.

Step 2: Parse Response and Fetch Product Details

Extract the brand logo and product IDs, then fetch product details from your shop:

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 data from ad server
const bannerImage = adm.fields?.bannerImage || null;   // Brand logo URL
const offers = adm.fields?.feed?.offers || [];          // Array with product_id

// 2. Extract product IDs
const productIds = offers.map(offer => offer.product_id);
console.log('Product IDs from ad server:', productIds);

// 3. Fetch product details from YOUR shop database
const shopProducts = await fetchProductsFromDatabase(productIds);

// 4. Combine shop data with ad server tracking URLs (base URL + offer parameter)
// If meta.adclick is empty, use offer_url directly (without encoding)
const products = shopProducts.map((product, index) => ({
    ...product,
    trackingUrl: adm.meta.adclick
        ? adm.meta.adclick + encodeURIComponent(offers[index]?.offer_url || '')
        : (offers[index]?.offer_url || '')
}));

// 5. Extract DSA transparency info
const dsaInfo = bid.ext?.dsa ? {
    advertiser: bid.ext.dsa.behalf,
    payer: bid.ext.dsa.paid
} : null;

// 6. Prepare data for rendering
const adData = {
    bannerImage,              // From ad server
    products,                 // From YOUR shop + trackingUrl from ad server
    dsaInfo,
    rawResponse: bidResponse,
    containerId: 'branded-products-container'
};

/**
 * Fetch product details from your database
 * @param {string[]} productIds - Product IDs from ad server
 */
async function fetchProductsFromDatabase(productIds) {
    // Replace with your actual database query
    const products = await db.products.findMany({
        where: { id: { in: productIds } },
        select: {
            id: true,
            imageUrl: true,
            title: true,
            price: true,
            detailPageUrl: true
        }
    });

    return products.map(p => ({
        product_id: p.id,
        image: p.imageUrl,
        name: p.title,
        price: p.price,
        url: p.detailPageUrl
    }));
}

Step 3: Render HTML + Register Response

Render the branded products banner combining ad server data (logo) with your shop data (products):

<!-- Render branded products banner -->
<div id="branded-products-container" class="branded-products">
    <div class="sponsored-header">
        <span class="sponsored-badge">Sponsored</span>
        <% if (adData.dsaInfo) { %>
        <div class="dsa-info">
            <span>Advertiser: <%= adData.dsaInfo.advertiser %></span>
            <span>Paid by: <%= adData.dsaInfo.payer %></span>
        </div>
        <% } %>
    </div>

    <!-- Brand logo from AD SERVER -->
    <div class="brand-section">
        <img class="brand-logo" src="<%= adData.bannerImage %>" alt="Brand Logo">
    </div>

    <!-- Product details from YOUR SHOP + tracking URLs from AD SERVER -->
    <div class="products-section">
        <% adData.products.slice(0, 5).forEach(function(product) { %>
        <a href="<%= product.url %>" class="product-tile"
           target="_blank" rel="noopener nofollow sponsored"
           onclick="new Image().src='<%= product.trackingUrl %>';">
            <img src="<%= product.image %>" alt="<%= product.name %>">
            <div class="product-name"><%= product.name %></div>
            <div class="product-price">
                <%= new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(product.price) %>
            </div>
        </a>
        <% }); %>
    </div>
</div>

<!-- Register for viewability and impression tracking -->
<script>
    dlApi.cmd.push(function () {
        dlApi.registerBidResponse(<%- JSON.stringify(adData.rawResponse) %>, 'branded-products-container');
    });
</script>

What the Ad Server Returns

FieldDescription
ad.fields.bannerImageBrand logo URL (render directly)
ad.fields.feed.offersArray of product objects
ad.fields.feed.offers[].product_idProduct ID - use to fetch details from your shop
ad.fields.feed.offers[].offer_urlClick tracking parameter - append to meta.adclick for full tracking URL
ad.dsa.behalfAdvertiser name (DSA compliance, requires dsainfo: 1)
ad.dsa.paidEntity that paid for the ad (DSA compliance)
ad.meta.adclickClick tracking base URL
ad.meta.adidAd identifier

Key difference from Brand Store: The ad server provides the brand logo AND tells you which products to display (via product_id). Your shop must fetch the actual product details (image, price, name, URL) from your own product database.


Related