Post View Conversions on Private Auction Wins

Image 1 Image 2 Image 3 Image 4
Here we see a satisfied customer with some delivered food, they found by ads, without the aid of third party cookies. - iSac, ChatGPT, April 12, 2024.

Post View Conversions on Private Wins

Test Home

Overview

Here we go over a solution that will help us maintain the current functionality we have for post-view conversions, during the "intermediate sandbox" stage.

Prereqs

If you are unfamiliar with the Post View Conversions for Contextual Wins solution I suggest you start there, as this solution builds on that one with a little more complexity to handle the private rendering issue. That page will give you relevant background on the features we're trying to support and testing setup as well.

At a minimum have your chrome setup for PS testing.

Problem

With 3PCD we no longer have a consistent identifier to use across sites in the same browser. ARA provides some of the functionality we need, but does not provide a solution that gives us an immediate, event level, and un-noised signal, which is needed for CPA billing.

An extra challenge for post-view conversions after a private win is that Vanilla Privacy Sandbox provides less information inside the private rendering frame than is available in general, in particular information from the contextual auction such as auction ID, tag ID, etc, so using those fields as join keys between imps and conversions requires extra footwork in this case.

Goal

The goal for a solution is to be able to join one or more AuctionTargets, in this case the impression that a user saw, to a ConversionTrigger, i.e. a purchase, immediately, at event level granularity, and without noise. We also must not link any identifiers, assuming the AuctionTarget and ConversionTrigger are in different sites.

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.
  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 query params from (1), which works due to same origin.
  4. Register an IG with the AuctionTarget information passed in during (1).
  5. On the Advertisers page, Register an IG with the relevant conversion information on conversion.
  6. "Activate" the IGs from the first two steps by running an auction which includes only those two.
  7. The "auction" results in a single call to the IGs KV service with the relevant auction and conversion information, which can then be joined as normal with the original auction information.

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. Create the AuctionTarget IG using the client request and corresponding auction id, as well as the pixel ID, with priorityVector set up to filter on that pixel ID later during conversion triggering.
      2. For the purpose of this test, we will also store IGs for "other pixels", to show that the appropriate filtering to a single pixel IG is not merely the result of only 2 IGs being registered.
      3. Inject the ultimately targeted actual creative url.

    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-view/private-win/render).

  5. Advertiser Page - Conversion Time: We then visit the advertisers site 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 from step 4d. The code on page then does two things in order:
    1. Drop an interest group with the pixelID (and other useful metadata).
    2. Run an auction that invites the 2 IGs for this pixel we've mentioned so far (the impression based one from the publisher site and the pixel based one from the advertiser site).
    3. That auction with those 2 IGs results in a call to the Ad Tech KV server with information from those two IGs, which forwards that log to the Ad Tech Data Pipeline. We can see them together here, which could then be used for transaction resolution.

    The client side JS that runs steps 5a and 5b is here.

  6. Tear Down/Clean Up: Finally we leave the "Conversion Trigger" IG to not leave any mess around.

    The clean up is the last line here.

Results

You can see expected results here.

Scope and Limitations

Scope

Limitations

The major limitation here that does not apply to the contextual win solution is that this, as described, will not work DSPs and SSPs that are different entities.

See here for details on limitations of this solution. Note that for this solution as described today, without support for different entities as DSP and SSP, the permissions issue mentioned does not apply.

Further Details/Ideas to Consider

See here.