prebid / Prebid.js

Setup and manage header bidding advertising partners without writing code or confusing line items. Prebid.js is open source and free.
https://docs.prebid.org
Apache License 2.0
1.33k stars 2.09k forks source link

Prebid.js, UnifiedID, and other user IDs #3046

Closed bretg closed 5 years ago

bretg commented 6 years ago

Overview

UnifiedID is a way of establishing 'universal' user device IDs for web browsers instead of having dozens of exchanges sync IDs with hundreds of demand sources. The basic concept is that the UnifiedID organization stores a set of global IDs at several locations and each publisher using Prebid.js should be able to take advantage of UnifiedID to get better bids from supporting DSPs.

Note that UnifiedID is not a single ID, but potentially a set of IDs. At first just from The TradeDesk, but someday perhaps others.

There are several main steps in the process:

  1. Javascript in the browser looks in local storage (cookie or HTML storage) for the set of IDs. If it can't find them locally, it connects to the ID server to retrieve the values for this user, then stores them locally.
  2. Any ad requests in the page should send the IDs to all adapters. Those that support IDs pass the values to their servers.
  3. Supporting SSPs send the IDs to DSPs that support the ID

Note that universal user IDs aren't needed in the mobile app world because device ID is available in those ad serving scenarios.

Prebid, Unified ID, and other User IDs

Prebid.org intends to support integration of Unified ID and other 'universal' IDs as a core feature in header bidding products with appropriate publisher-level controls. We will also deprecate the "pubCommonId", folding it's functionality into this more generic module. (The existing module will be left for a few months as there will be page changes to make.)

Prebid support of IDs should include:

The role of Prebid.js is to:

The role of Prebid Server is to:

Then it's up to each adapter to read the ID values from a standard location and forward it through their pipeline.

Use Cases

Examples of the proposed publisher configuration for various scenarios follows:

1) Publisher supports Unified ID and first party domain cookie storage

pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            params: {
                partner: "PARTNER_CODE",
                url: "URL_TO_UNIFIED_ID_SERVER"
            },
            storage: {
                type: "cookie",  
                name: "pbjs-unifiedid",       // create a cookie with this name
                expires: 60                        // cookie can last for 60 days
            }
        }],
        syncDelay: 5000       // 5 seconds after the first bidRequest()
    }
});

2) Publisher supports UnifiedID with HTML5 local storage, synchronously with the first PBJS

pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            params: {
                partner: "PARTNER_CODE",
                url: "URL_TO_UNIFIED_ID_SERVER"
            },
            storage: {
                type: "html5",
                name: "pbjs-unifiedid"    // set localstorage with this name
            },
            maxDelayToAuction: 500 // implies syncDelay of 0
                           // wait up to 500ms before starting auction
        }]
    }
});

3) Publisher has integrated with unifiedID on their own and wants to pass the unifiedID directly through to Prebid.js

pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            value: {"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3", 
                     "appnexus_id": "1234"}
        }]
    }
});

4) Publisher supports PubCommonID and first party domain cookie storage

pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "pubCommonId",
            storage: {
                type: "cookie",  
                name: "_pubCommonId",       // create a cookie with this name
                expires: 1825                           // expires in 5 years
            }
        }]
    }
});

5) Publisher supports both unifiedID and PubCommonID and first party domain cookie storage

pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "unifiedId",
            params: {
                partner: "PARTNER_CODE",
                url: "URL_TO_UNIFIED_ID_SERVER"
            },
            storage: {
                type: "cookie",  
                name: "pbjs-unifiedid"       // create a cookie with this name
            }
        },{
            name: "pubCommonId",
            storage: {
                type: "cookie",  
                name: "pbjs-pubCommonId"       // create a cookie with this name
            }
        }],
        syncDelay: 5000       // 5 seconds after the first bidRequest()
    }
});

6) DigiTrust example

pbjs.setConfig({
    usersync: {
        userIds: [{
            name: "digitrust",
            params: {
                memberId: "123abc"
            },
            storage: {
                type: "cookie",  
                name: "pbjs-digitrust"       // create a cookie with this name
            }
        }],
        syncDelay: 5000       // 5 seconds after the first bidRequest()
    }
});

Design Notes

Prebid.js will use the setConfig values configured by the publisher to look for locally stored ID values. If it doesn't find local IDs, it will reach out to the configured URLs, storing any results for the specified number of days as appropriate after checking for GDPR consent.

The prebidServerBidAdapter adds the values to the user.ext.eids section of the OpenRtb2 protocol:

{
    "user": {
        "ext": {
            "eids": [{
                "source": "adserver.org",
                "uids": [{
                    "id": "111111111111",
                    "ext": {
                        "rtiPartner": "TDID"
                    }
                }]
            },
            {
                "source": "pubcommon",
                "uids": [{
                    "id":"11111111"
                }]
            }
            ],
            "digitrust": {
                "id": "11111111111",
                "keyv": 4
            }
        }
    }
}```

Suggested Pseudo-Code
1. The User ID Module (UIM) looks for its config. If no config, exit. Nothing to do.
1. UIM should check the status of the GDPR and exit if the user doesn't consent to local storage of data. Specifically, if the PBJS consentManagement module is present, then the code needs to assume the user does not consent unless it sees a consent string that specifically allows Purpose 1 (store local state). If consent is required but not found, the module should just exit after logging a debug warning for when pbjs_debug=true.
1. For each userId specified in the config, check the appropriate local storage to see if we already have the ID JSON
1. If we don't have local storage for any of the specified userIds, set up a timer to initiate the call the appropriate sub-module 'getId function'. This function has access to the gdprConsent info if available. The responses should cause the UIM to store the JSON return data into the appropriate local storage.
1. If we do have local storage for a sub-module, call its 'decoding function' and build up the object that will be sent to the adapters. e.g. bidRequest.userIds={"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3", "appnexus_id": "1234", "pubcid": "c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6", digitrustId: {"id": "skloi903409as9kf8cj", "keyv": 4}}
1. As an example implementation, modify the Rubicon PBJS adapter to read 'bidRequest.userIds.tdid' and pass it through to the exchange.

Each of the "sub-modules" in the User ID Module has these interfaces:
- A get-ID function. This ID-specific function will be called when it's time to retrieve the ID from that service. It will be passed the `params` object from config. The response is assumed to return JSON that will be directly stored by the module in the appropriate local storage with the appropriate expiration date. Note that expiration may be specified in either the `expires` field of the publisher config, OR overridden by the `expires` field on the JSON response from the ID service.
- A JSON decoding function. This ID-specific function will be called when the module is compiling the object that will be passed to all of the adapters. It takes the full data stored in local state for the sub-module and returns the string or object that should be added to the adapter-visible ID object. In most cases, 

<a name="spec"></a>
## Spec

### Implementation

module

test if local storage/cookies are enabled
* if neither is enabled, exit
* else add enabled types to enabledStorageTypes

test if any user ids are set in configuration
* if none exist, exit

iterate sub-modules, for each submodule
1. check if configuration exists with matching sub-module config name: 
  * skip sub-module if none exists

2. validate sub-module config storage props and params
  * syncDelay (optional)

  * storage (required)
    * type (required)
    * name (required)
    * expires (required)

  * params (optional)
    * partnerCode
    * url

3. if syncDelay exists, use setTimeout with callback wrapping next function, else call immediately

4. use storage key and type to retrieve stored value
  * if stored value exists, add value to data array for adding to bid request
  * else, call sub-module getId method to retrieve

/**

/**

/**

/**

/**

/**

/**