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.
dlApi.cmd.push(function (dlApi) {
dlApi.registerBidResponse(adm, 'ad-container-id');
});Applies to all backend (SSR) formats: for example: Sponsored Single Tile, Sponsored Product Slider, Brand Store, Branded Products. The rules on this page apply identically to all formats.
The DL API SDK must be loaded on the page for this to work.
dlApi.cmd.push()queues the call until the SDK is ready — do not calldlApi.registerBidResponse()directly outside ofdlApi.cmd.push().
Which adm to pass
Extract the adm for the rendered slot and pass it directly to registerBidResponse().
Single slot (most formats):
// Extract adm for the rendered slot — inject this into the page
const adm = bidResponse.seatbid[0].bid[0].adm;
admis the ad markup string returned by the bidder. It may be a JSON string or a pre-parsed object — the SDK accepts both.
Multi-slot — find the bid by impid:
// For multi-slot: find the bid matching the rendered slot's impid
const adm = bidResponse.seatbid
.flatMap(seat => seat.bid)
.find(b => b.impid === renderedImpid)?.adm;See API reference for the required adm payload structure.
Filter offers to match displayed products
The adm payload may contain more offers in adm.fields.feed.offers than your page actually renders. For example, the bidder may return 3 offers (via offers_limit), but your page displays only 1. Before calling registerBidResponse(), filter the offers array so it contains only the offers that were actually rendered.
registerBidResponse()fires tracking events for every offer inadm.fields.feed.offers. Passing unrendered offers inflates impression metrics — the campaign reports will count impressions for products the user never saw.
Single displayed offer (most common):
// adm from the bid response — parse it if it's a JSON string
const adm = typeof bid.adm === 'string' ? JSON.parse(bid.adm) : bid.adm;
const offers = adm.fields?.feed?.offers || [];
// Your shop picks one offer to display (e.g. the first available product)
const displayedOffer = offers.find(offer =>
isProductAvailableInShop(offer.product_id)
);
if (!displayedOffer) {
// No matching offer found — do not render, do not call registerBidResponse
return;
}
// Keep only the displayed offer
adm.fields.feed.offers = [displayedOffer];
// Render the tile...
renderTile(displayedOffer);
// Pass the filtered adm — the SDK accepts a parsed object
dlApi.cmd.push(function (dlApi) {
dlApi.registerBidResponse(adm, 'ad-container-id');
});Subset of displayed offers (e.g. carousel showing 3 out of 5):
const adm = typeof bid.adm === 'string' ? JSON.parse(bid.adm) : bid.adm;
const allOffers = adm.fields?.feed?.offers || [];
// Your page displays at most 3 products
const MAX_DISPLAY = 3;
const displayedOffers = allOffers.slice(0, MAX_DISPLAY);
// Keep only the displayed offers
adm.fields.feed.offers = displayedOffers;
// Render products...
renderProducts(displayedOffers);
// Pass the filtered adm
dlApi.cmd.push(function (dlApi) {
dlApi.registerBidResponse(adm, 'ad-branded-products');
});The SDK accepts
admas a JSON string or a pre-parsed object. After filtering, you can pass the modified object directly — no need to re-stringify.
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
- Count the impression and viewable impression in Ring DAS campaign reporting
When to call it
Call registerBidResponse() only when the ad tile is actually rendered.
| Scenario | Call registerBidResponse()? |
|---|---|
| Ad fetched and rendered | Yes |
| Ad fetched but ad no rendered — slot stays empty | No |
| Shop business logic prevents displaying the ad | No |
| 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):
| Rule | Description |
|---|---|
| One call per slot | Each rendered slot requires its own registerBidResponse() call with its own container element ID |
| Pass the adm for that slot | Extract and pass only the adm for the specific rendered slot |
| Skip unrendered slots | Do not call registerBidResponse() for slots that were not rendered — see When to call it |
The server injects an adm per slot into the page (or null if the slot was not rendered). The browser calls registerBidResponse() only for slots that were actually rendered:
// admSlot1/2/3: adm strings injected by the server — null if slot was not rendered
dlApi.cmd.push(function (dlApi) {
if (admSlot1) { dlApi.registerBidResponse(admSlot1, 'ad-sponsored-1'); }
if (admSlot2) { dlApi.registerBidResponse(admSlot2, 'ad-sponsored-2'); }
if (admSlot3) { dlApi.registerBidResponse(admSlot3, 'ad-sponsored-3'); }
});Passing
nullor 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
// adm: the ad markup for this specific slot
// containerId: the HTML element ID of the rendered ad container
const slotData = {
adm: bidResponse.seatbid[0].bid[0].adm,
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) {
dlApi.registerBidResponse(adSlotData.adm, adSlotData.containerId);
});Multi-slot pattern:
// adSlotsData: array of { adm, containerId } injected by the server
// adm is null for slots that were not rendered
dlApi.cmd.push(function (dlApi) {
adSlotsData.forEach(function (slot) {
if (slot.adm) {
dlApi.registerBidResponse(slot.adm, slot.containerId);
}
});
});API reference
dlApi.registerBidResponse(adm, containerId)
| Parameter | Type | Description |
|---|---|---|
adm | string|object | The adm value from the bid — JSON string or pre-parsed object |
containerId | string | The HTML id attribute of the element containing the rendered ad |
adm payload
The adm may be passed as a JSON string or an already-parsed object — the SDK accepts both. All other fields from the original bidder adm are optional — if payload size is a concern, strip them 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
- Ad Preview & Debug Mode — Test SSR integrations without running live campaigns
- Sponsored Product Slider — Multi-slot SSR format with per-slot adm registration
- Sponsored Single Tile — SSR format for search and category pages
- Brand Store (Web) — SSR format for product detail pages
- Branded Products — SSR format for homepage banners
Updated 4 days ago
