Register Bid Response

Viewability tracking and impression counting via dlApi.registerBidResponse() — applies to all Ring DAS SSR ad formats

Register Bid Response

dlApi.registerBidResponse() is the SDK call that activates viewability tracking and counts an ad impression. It must be called in the browser after the ad is rendered — not on the server side.

Applies to all backend (SSR) formats: Sponsored Single Tile, Sponsored Product Slider, Brand Store, Branded Products. The rules on this page apply identically to all formats.


Filter before registering

⚠️

Always ensure exactly one bid is passed to registerBidResponse(). Only the first seatbid[0].bid[0] entry is processed. Passing the full response with multiple seatbids registers one impression per seatbid, not one per rendered slot.

There are two ways to handle this:

  • Format A (full OpenRTB response) — filter the response on the server side before injecting it into the page, so only the seatbid for the rendered slot is passed:
/**
 * Filter the raw response to only the seatbid containing the given impid.
 * Pass this filtered response to registerBidResponse() — not the full rawResponse.
 */
function filterResponse(rawResponse, impid) {
    return {
        ...rawResponse,
        seatbid: rawResponse.seatbid.filter(seat =>
            seat.bid.some(b => b.impid === impid)
        ),
    };
}
  • Format B (single bid object) — extract just the bid object on the server side and pass it directly. This eliminates the need for filterResponse() entirely and reduces the injected payload size.

For single-slot formats (Brand Store, Branded Products), the full raw response contains only one seatbid, so filtering is not required with Format A. You may still filter as a defensive practice, or switch to Format B.


What it does

When called, registerBidResponse() instructs the DL API SDK to:

  • Start observing the ad container for viewability (using IntersectionObserver)
  • Fire an impression event once the ad is visible on screen
  • Count the impression in Ring DAS campaign reporting

The SDK must be loaded on the page for this to work. Even for SSR formats where the server fetches the ad and renders it, the frontend SDK must still be present to handle the viewability measurement.


When to call it

Call registerBidResponse() only when the ad tile is actually rendered.

ScenarioCall registerBidResponse()?
Ad fetched and renderedYes
Ad fetched but ad no rendered — slot stays emptyNo
Shop business logic prevents displaying the adNo
Server returned 204 (no ad)No

Calling registerBidResponse() for an empty slot counts a false impression, inflating campaign metrics incorrectly.


Per-slot rules for multi-slot formats

For formats that support multiple ad slots (Sponsored Product Slider, Sponsored Single Tile with multiple positions):

RuleDescription
One call per slotEach rendered slot requires its own registerBidResponse() call with its own container element ID
Filter before registeringPass only the seatbid for the specific slot, not the full bid response
Skip unrendered slotsDo not call registerBidResponse() for slots that were not rendered — see When to call it

The server injects a filtered response per slot into the page (or null if the slot was not rendered). The browser calls registerBidResponse() only for slots that were actually rendered:

// filteredResponseSlot1/2/3: injected by the server — null if slot was not rendered
dlApi.cmd.push(function (dlApi) {
    if (filteredResponseSlot1) {
        dlApi.registerBidResponse(filteredResponseSlot1, 'ad-sponsored-1');
    }
    if (filteredResponseSlot2) {
        dlApi.registerBidResponse(filteredResponseSlot2, 'ad-sponsored-2');
    }
    if (filteredResponseSlot3) {
        dlApi.registerBidResponse(filteredResponseSlot3, 'ad-sponsored-3');
    }
});
⚠️

Passing null or an empty object is not a safe no-op — skip the call entirely for unrendered slots.


SSR pattern

The server resolves the ad, renders the HTML, and injects the bid data into the page. The browser SDK then picks it up after load.

Server side (Node.js / EJS example):

// After fetching and resolving the bid response, inject data into the page
// filteredResponse: the seatbid filtered for this specific slot
// containerId: the HTML element ID of the rendered ad container
const slotData = {
    filteredResponse: filterResponse(bidResponse, matchedBidImpid),
    containerId: 'ad-product-slider-slot-1',
};
<script>
  // Injected by server
  const adSlotData = <%- JSON.stringify(slotData) %>;
</script>

Browser side (runs after SDK loads):

dlApi.cmd.push(function (dlApi) {
    // Pass filteredResponse — only the seatbid for this specific slot.
    // Do NOT pass the full rawResponse with all seatbids.
    dlApi.registerBidResponse(adSlotData.filteredResponse, adSlotData.containerId);
});

Multi-slot pattern:

// adSlotsData: array of { filteredResponse, containerId } injected by the server
// filteredResponse is null for slots that were not rendered
dlApi.cmd.push(function (dlApi) {
    adSlotsData.forEach(function (slot) {
        if (slot.filteredResponse) {
            dlApi.registerBidResponse(slot.filteredResponse, slot.containerId);
        }
    });
});

API reference

dlApi.registerBidResponse(bidResponse, containerId)
ParameterTypeDescription
bidResponseobjectFull OpenRTB response or a single bid object (see formats below)
containerIdstringThe HTML id attribute of the element containing the rendered ad

Accepted input formats

registerBidResponse() auto-detects the input format based on whether adm is present at the top level.

Format A — full OpenRTB response (or filtered subset):

{
  seatbid: [{
    bid: [{
      adm: string | object,    // Required — JSON string or pre-parsed object
    }]
  }]
}

Format B — single bid object:

{
  adm: string | object,    // Required — JSON string or pre-parsed object
}

Extract the bid object for the rendered slot on the server side and inject only that — no filterResponse() needed, and the injected payload is smaller.

adm payload

The adm field may be a JSON string or an already-parsed object — the SDK accepts both formats automatically. All other fields from the original bidder adm are optional — if payload size is a concern, you may strip them on the server side before injecting into the page.

{
  adserver_meta: {   // Required — pass whole object
    ...
  },
  meta: {            // Required — pass whole object
    ...
  },
  gctx: string,      // Required
  lctx: string       // Required
}

Related