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), logo click URL (urlRedirect, optional), 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

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 homepage
    Page->>DAS: 2. Fetch Ad (fetchNativeAd / bidder)
    DAS-->>Page: Brand logo + product IDs

    Note over Page: 3. Extract product IDs from response

    Page->>ShopAPI: 4. Fetch product details for IDs
    ShopAPI-->>Page: Product images, prices, names, URLs

    Note over Page: 5. Combine:<br/>- Logo from DAS<br/>- Products from Shop<br/>- Tracking from DAS

    Page->>User: 6. Render branded products banner
    Note over Page: Fire impression tracking

    User->>Page: 7. Click product
    Page->>DAS: Fire click tracking
    Page->>User: Redirect to product page

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
targetSite and area identifier (see details below)
tidYour Ring DAS tenant ID (format: EA-XXXXXXX)
dsainfoSet 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/HOMEPAGE)

Examples:

  • Desktop homepage: DEMO_PAGE/HOMEPAGE
  • Mobile product page: M_DEMO_PAGE/PRODUCT_DETAIL
  • Desktop category listing: DEMO_PAGE/LISTING
<script>
    dlApi = {
        target: "DEMO_PAGE/HOMEPAGE", // 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
});
ParameterValueDescription
npa0Consent given - personalized ads enabled
npa1No 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-ValueDescription
offer_idsProduct IDs currently displayed on the page
category_idsCategory IDs for the current page context
main_category_idMain product category ID - primary category used for ad matching (product may belong to multiple categories, but has one main)
IPPage View ID — constant identifier for the current page load
IVPage View Unique ID — changes on every SPA view transition; equals IP on traditional pages
TABIDBrowser Tab Identifier — stable ID per browser tab session
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
    dlApi.addKeyValue('IP', '202603041502158687149647');            // page view ID
    dlApi.addKeyValue('IV', '202603041502158687149647');            // page view unique ID (changes on SPA transitions)
    dlApi.addKeyValue('TABID', 'tab-7f3e2a');                      // browser tab identifier
});

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 fetch the ad. The ad server returns the brand logo and product IDs - you must fetch product details from your shop.

ParameterDescription
slotFixed value display 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
opts.offers_limitMaximum number of products returned from ad server
dlApi.cmd.push(function (dlApi) {
    dlApi.fetchNativeAd({
        slot: 'display',                            // fixed value for Branded Products
        div: 'ad-branded-products',          // 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 urlRedirect = ad.fields?.urlRedirect || '';      // Logo click URL (optional)
        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
        const productsWithTracking = productDetails.map((product, index) => ({
            ...product,
            trackingUrl: offers[index]?.adclick || ''
        }));

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

        // 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 5: 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 6: Render the Ad

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

function renderBrandedProducts(bannerImage, products, dsaInfo, urlRedirect) {
    const container = document.getElementById('ad-branded-products');
    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">
            ${urlRedirect
                ? `<a href="${escapeHtml(urlRedirect)}" target="_blank" rel="noopener nofollow sponsored">
                      <img class="brand-logo" src="${escapeHtml(bannerImage)}" alt="Brand Logo">
                   </a>`
                : `<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.

Request Configuration

ParameterDescription
BIDDER_URLBidder endpoint URL - provided by your Ring DAS account manager
NETWORK_IDYour Ring DAS network ID (e.g., 1746213 for Branded Products)
tmaxRequest timeout in milliseconds
ext.srcOptional. Set to s2s for server-to-server requests
ext.is_non_prebid_requestSet to true to receive the response in the correct server-to-server format (not Prebid.js format)

Placement Parameters

These parameters define WHERE the ad appears and WHICH format to serve.

ParameterDescription
site.idYour site identifier in Ring DAS
site.ext.areaPage type context, UPPERCASE: HOMEPAGE, PRODUCT_DETAIL, LISTING, SEARCH
imp[].tagidFixed value display for this format

Targeting Parameters

ParameterDescription
ext.offeridsList of product offer IDs from current page for contextual targeting and relevancy (optional)
ext.categoryidsProduct category IDs for contextual targeting — applies globally to all impressions in the request (optional)
ext.excluded_offer_idsList of offer IDs already shown organically on the page — prevents the same offer appearing as both organic and sponsored (optional)
ext.keyvalues.IPPage View ID — constant for the lifetime of a page load. See API Parameters Reference
ext.keyvalues.IVPage View Unique ID — changes on every SPA view transition; equals IP on traditional pages. See API Parameters Reference
ext.keyvalues.TABIDBrowser Tab Identifier — stable ID per browser tab session. See API Parameters Reference
ext.keyvalues.ab_variantA/B test variant identifier — string identifying which experiment variant the user is in (optional)

Consent & Privacy Parameters

ParameterDescription
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

Ad Behavior Parameters

ParameterDescription
imp[].ext.no_redirectSet to true if adclick URLs should only fire tracking pixel without redirect (shop handles redirect via href)

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.

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.

ParameterDescription
user.eidsOpenRTB Extended Identity Array — pass user identifiers with per-ID GDPR consent metadata. Supports session-level (atype: 500, no consent required) and device-level (atype: 1, consent required) identifiers. See API Parameters Reference for full field reference.
user.ext.ids.luLocal user ID from the ea_uuid cookie on your shop's domain (set by Ring DAS). Must follow the alphanumeric format (e.g., 202408221036415499301131).

User identifiers:

  • user.eids — Pass your own session and/or tracking identifiers with GDPR consent metadata. Provide your session ID as atype: 500 (no consent required) and your device/tracking ID as atype: 1 (consent required).
  • user.ext.ids.lu — The value from the ea_uuid cookie set by Ring DAS. Used as Ring DAS's internal session identifier.

Both can be sent simultaneously. See API Parameters Reference for the full user.eids field reference.

// Node.js backend
const NETWORK_ID = `${NETWORK_ID}`;  // Branded Products network ID
const BIDDER_URL = `https://das.idealo.com/${NETWORK_ID}/bid`;

// User identifiers — replace with actual values from your system
const SESSION_ID = 'your-session-id';      // session-level identifier (no GDPR consent required)
const TRACKING_ID = 'your-tracking-id';    // device-level tracking identifier (GDPR consent required)

const requestBody = {
    id: Math.random().toString(16).substring(2, 15),
    imp: [{
        id: 'imp-1',
        tagid: 'display',                           // 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 (UPPERCASE)
        ext: {
            area: 'HOMEPAGE'                        // page type (UPPERCASE): HOMEPAGE, PRODUCT_DETAIL, LISTING, SEARCH
        }
    },
    user: {
        eids: [{
            source: 'your-domain.com',
            inserter: 'your-domain.com',
            uids: [
                {
                    id: SESSION_ID,
                    atype: 500,
                    ext: { id_type: 'session', consent_required: false }
                },
                {
                    id: TRACKING_ID,
                    atype: 1,
                    ext: { id_type: 'tracking', consent_required: true }
                }
            ]
        }],
        ext: {
            npa: false,                             // false = consent given, true = no consent
            ids: {
                lu: '202408221036415499301131'      // Ring DAS internal session ID (from ea_uuid cookie)
            }
        }
    },
    ext: {
        network: NETWORK_ID,
        keyvalues: {
            IP: '202603041502158687149647',          // page view ID
            IV: '202603041502158687149647',          // page view unique ID
            TABID: 'tab-7f3e2a',                    // browser tab identifier
            ab_variant: 'variant-b',                // optional: A/B test variant identifier
        },                                          // optional targeting parameters
        offerids: ['2345', '67890'],                // optional: product IDs from current page
        categoryids: [101, 205],                    // optional: category IDs for targeting
        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');
}

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 urlRedirect = adm.fields?.urlRedirect || null;   // Logo click URL (optional)
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
const products = shopProducts.map((product, index) => ({
    ...product,
    trackingUrl: offers[index]?.adclick || ''
}));

// 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
    urlRedirect,              // Logo click URL from ad server (optional)
    products,                 // From YOUR shop + trackingUrl from ad server
    dsaInfo,
    adm: adm,
    containerId: 'ad-branded-products'
};

/**
 * 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="ad-branded-products" 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">
        <% if (adData.urlRedirect) { %>
        <a href="<%= adData.urlRedirect %>" target="_blank" rel="noopener nofollow sponsored">
            <img class="brand-logo" src="<%= adData.bannerImage %>" alt="Brand Logo">
        </a>
        <% } else { %>
        <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.adm) %>, 'ad-branded-products');
    });
</script>
⚠️

registerBidResponse() counts impressions. Only call it when the ad is rendered — never for empty slots. For multi-slot patterns, see Register Bid Response.


What the Ad Server Returns

FieldDescription
ad.fields.bannerImageBrand logo URL (render directly)
ad.fields.urlRedirectBrand logo click destination URL (optional). When present, wrap the brand logo in a link to this URL.
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[].adclickClick tracking URL — fire as pixel on click
ad.fields.feed.offers[].offer_custom_fieldsOptional. Object with custom fields for the offer. May contain manufacturer_id — the manufacturer identifier for the offered product. Both the object and individual fields may be absent
ad.dsa.behalfAdvertiser name (DSA compliance, requires dsainfo: 1)
ad.dsa.paidEntity that paid for the ad (DSA compliance)
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.


Preview & Test Your Ads

Ad Preview & Debug Mode serves two distinct audiences — developers integrating the format and business stakeholders managing live campaigns.

For developers: Test backend integrations and verify ad rendering without running live campaigns. Use query string overrides (test_site, test_area, test_kwrd) to simulate different targeting contexts and adbeta to force a specific creative from any line item.

For advertisers and campaign managers: Preview a specific creative on a real publisher page before campaign launch — or verify it after changes. No development tools or server access required. Share a single URL (with the right query parameters) and anyone opening that link sees the exact creative that will run in production.

See Ad Preview & Debug Mode for the full parameter reference, backend implementation guide, and end-to-end tracking with X-ADP-EVENT-TRACK-ID.


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 ad slots on a single page, include multiple impressions in one request with unique IDs.

Backend (Bidder API):

const requestBody = {
    id: 'request-123',
    imp: [
        {
            id: 'imp-0',                            // unique ID for first slot
            tagid: 'display',
            secure: 1,
            native: { request: "{}" },
            ext: { pos: 1, offers_limit: 5 }
        },
        {
            id: 'imp-1',                            // unique ID for second slot
            tagid: 'display',
            secure: 1,
            native: { request: "{}" },
            ext: { pos: 2, offers_limit: 5 }
        }
    ],
    // ... 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
    // Render ad for this specific slot
});
⚠️

registerBidResponse() counts impressions. Only call it when the ad is rendered — never for empty slots. For multi-slot patterns, see Register Bid Response.


Related


What’s Next

Learn how to integrate the Sponsored Product / Offer (Single Tile) format