Post Click Conversions on Private Auction Wins

Image 1 Image 2 Image 3 Image 4
Here we see a satisfied customer with numerous purchases, connected by ads, without the aid of third party cookies. - iSac, ChatGPT, April 12, 2024.

Post Click Conversions on Private Wins

Test Home

Overview

Here we go over a solution that can enable maintenance of our current functionality for post click attribution, during the current "intermediate sandbox" stage.

Prereqs

Have your chrome setup for PS testing.

Problem

When a slot is won by a Private Auction, the creative must render in an iframe that gets the renderUrl from the IG as it was declared at "Join Time", which in particular means that no auction info such as a Click ID can be put on the iframe src directly.

Goal

The goal for a solution is to be able to get a Click ID into a click handler for the rendered creative in the creative iframe that renders the renderUrl of the private win.

Note, that here we'll assume a "click ID" could effectively be an auction_id, or something that uniquely identifies an auction.

Solution Overview

This solution will work for cases for when the private auction wins and the SSP and DSP share the same origin.

As is this will not work for cases where the SSP and DSP don't share the same origins, presumably b/c they are different entities. More work to be done to get that working.

Since this solution applies only to cases where the SSP and DSP are the same (or to be more precise, sharing the same IG origin) we'll refer to the "ad tech".

This solution relies on:

The steps at a high level are:

  1. Run the contextual auction on page, deciding to run the private auction, and creating a Private Auction Runner IFrame, putting query parameters in for relevant information, in particular Click ID.
  2. The Auction Runner IFrame runs the private auction, then renders the private winner in an iframe with opaque URN.
  3. The server side HTML rendering includes JS to grab the Click ID from (1), which works due to same origin.
  4. The click handler of the rendered image is decorated with the Click ID.
  5. When the user clicks on the image and is taken to the the Advertisers page, code on that page stores the Click ID in local storage.
  6. When the conversion occurs that Click ID is looked up, and passed to the pipeline for joining.

Test

Overview

See domain structure to understand the domains used here, with the only difference being that we use pst-ssp as the domain for the Ad Tech, since we are treating the SSP and DSP as the same for the scope of this solution.

Steps

Let's walk through the test. As you go through the steps and click the links, they will open in new tabs. The new tabs will give their own descriptions of what they are doing and display test results as makes sense.

  1. Setup: First we Clear the Ad Tech Data Pipeline, which for ease of inspection will result in a redirect to the imp and pixel log viewer, which should show no entries for impressions or pixels, so we start with a clean pipeline (no tricks!).

  2. Publisher Page - Contextual: Next we hit the publishers content which invokes the Ad Tech code on page:
    1. We start by creating a client_request_id which will be used to unify all auctions run here, contextual or private.
    2. We then call our contextual auction, including the client_request_id for logging server side.
    3. The contextual auction returns it's results, including an auction_id to identify each individual auction within a multi tag request.
    4. The Ad Tech code decides that the winning contextual bid is not high enough, and so it will run a private auction.

    You can see the client side JS that runs 2a, 2b, and 2d here. The server side JS that "runs the contextual auction" in 2c is here.

  3. Publisher Page - Private Auction: To run a private auction for a given slot, we execute the following steps:
    1. We load a "Private Auction Runner" in an iframe. The src of the Runner is:
      1. Same origin with the Ad Tech (which importantly for later will also be same origin for the rendering of the creative that wins the private auction).
      2. Has query params for a few categories of information:
        1. Join Keys: client_request_id and the corresponding slot auction_id.
        2. BidRequest: tag ID, size, TLD.
        3. ContextualResult: winning bid info for comparison.
    2. The Runner then takes the information from the query params and invokes the Private Auction. The BidRequest information and client_request_id and "corresponding auction_id" are then put into the auctionSignals (for use later in worklet reporting) as well as the ContextualResult info for bid comparison.
    3. In this case the Private Bid beats the Contextual Bid, and so the code renders the winning creative in an iframe using the opaque URN returned by the private auction.

    You can see the JS that runs these steps here. The bidding function which submits the bid is here.

  4. Publisher Page - Render Private Win: Since the winning creative is same origin as the Ad Tech, and therefore the Private Auction Runner IFrame, JS within the creative IFrame can read the href of the Private Auction Runner IFrame. This gives it access to all the query params mentioned in step 3.a.ii. It also has access to the pixel ID being targeted by the winning creative via it's own href. So we now can:
    1. Fire the impression tracker for the private auction using the reportWin API, which will include the client_request_id, and corresponding contextual auction id, in the auctionSignals we created in 3b.
    2. The browser renders the private winner renderUrl in a new iframe, which results in the renderUrl call hitting our server which dynamically creates the html document, including JS, using information from the parent iframe (like client_request_id) and also the rendering iframe (like the creative and pixel):
      1. Inject the ultimately targeted actual creative url.
      2. Add a click handler to the image which includes the client_request_id, auction_id, and pixel_id.

    Again you can verify that the "state of the pipeline" has the corresponding imp tracker log but no pixel logs.

    The reportWin function that is invoked by the fledge framework in 4a is here. The client side JS that runs 4b is here. The fastify handler that fills out the template is here (look for /conversions/post-click/private-win/render).

  5. Advertiser Page - Landing Time: We then click on the image which takes us to the advertisers site, where code on page takes the client_request_id, auction_id, and pixel_id from 4.b.ii and stores that information in local storage.
    The client side JS that runs step 5 is here.
  6. Advertiser Page - Conversion Time: Finally, after much shopping, we hit the conversion page and click the "buy and pay money" button for the advertised product. That button is attached to a pixel, that just so happens to be the one attached to the winning creative. The code on page then forms it's normal pixel tracker URL using those variables, and sends that back to the Ad Tech Data Pipeline. here, which could then be used for transaction resolution.
    The client side JS that runs step 6 is here.

  7. Tear Down/Clean Up: Optionally we clear the value from local storage (might be worth keeping around, no matter to this test).

    The clean up is the last line here.

Results

You can see expected results here.

Scope and Limitations

Scope

Limitations

The major limitation is that this will not work for DSPs and SSPs that are different entities.