Repository: prebid/prebid-server
Pull request #1396 (2020-07-14 05:12:06)

Facebook Only Supports App Impressions

SyntaxNode: FYI: @annahc  

Patch:
diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go
index f4091e4e..d9f6719f 100644
--- a/adapters/audienceNetwork/facebook.go
+++ b/adapters/audienceNetwork/facebook.go
@@ -54,6 +54,12 @@ func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *
 		}}
 	}
 
+	if request.Site != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: "Site impressions are not supported.",
+		}}
+	}
+
 	return this.buildRequests(request)
 }
 
@@ -143,10 +149,6 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error {
 		app := *out.App
 		app.Publisher = &openrtb.Publisher{ID: pubId}
 		out.App = &app
-	} else {
-		site := *out.Site
-		site.Publisher = &openrtb.Publisher{ID: pubId}
-		out.Site = &site
 	}
 
 	if err = this.modifyImp(imp); err != nil {
@@ -468,15 +470,11 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*
 		return &adapters.RequestData{}, []error{err}
 	}
 
-	// The publisher ID is either in the app object or the site object, depending on the supply of the request so we need
-	// to check both
+	// The publisher ID is expected in the app object
 	pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id")
 	if err != nil {
-		pubID, err = jsonparser.GetString(req.Body, "site", "publisher", "id")
-		if err != nil {
-			return &adapters.RequestData{}, []error{
-				errors.New("path [app|site].publisher.id not found in the request"),
-			}
+		return &adapters.RequestData{}, []error{
+			errors.New("path app.publisher.id not found in the request"),
 		}
 	}
 
diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go
index 6b4f698d..de33bd39 100644
--- a/adapters/dmx/dmx.go
+++ b/adapters/dmx/dmx.go
@@ -160,7 +160,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapte
 	}
 
 	headers := http.Header{}
-	headers.Add("Content-Type", "Application/json;charset=utf-8")
+	headers.Add("Content-Type", "application/json;charset=utf-8")
 	reqBidder := &adapters.RequestData{
 		Method:  "POST",
 		Uri:     adapter.endpoint + addParams(sellerId), //adapter.endpoint,
diff --git a/config/config.go b/config/config.go
index f33dba69..2e7f875b 100755
--- a/config/config.go
+++ b/config/config.go
@@ -793,7 +793,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25")
 	v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
-	v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2")
+	v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2")
 	v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com")
 	v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb")
 	v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1")
@@ -804,7 +804,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs")
 	v.SetDefault("adapters.ix.endpoint", "http://appnexus-us-east.lb.indexww.com/transbidder?p=184932")
 	v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server")
-	v.SetDefault("adapters.kubient.endpoint", "http://kbntx.ch/prebid")
+	v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid")
 	v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest")
 	v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2")
 	v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver")
diff --git a/config/util/loggers.go b/config/util/loggers.go
index 88702e68..d9aad43a 100644
--- a/config/util/loggers.go
+++ b/config/util/loggers.go
@@ -4,18 +4,18 @@ import (
 	"math/rand"
 )
 
-type logMsg func(string, ...interface{})
+type LogMsg func(string, ...interface{})
 
 type randomGenerator func() float32
 
 // LogRandomSample will log a randam sample of the messages it is sent, based on the chance to log
 // chance = 1.0 => always log,
 // chance = 0.0 => never log
-func LogRandomSample(msg string, logger logMsg, chance float32) {
+func LogRandomSample(msg string, logger LogMsg, chance float32) {
 	logRandomSampleImpl(msg, logger, chance, rand.Float32)
 }
 
-func logRandomSampleImpl(msg string, logger logMsg, chance float32, randGenerator randomGenerator) {
+func logRandomSampleImpl(msg string, logger LogMsg, chance float32, randGenerator randomGenerator) {
 	if chance < 1.0 && randGenerator() > chance {
 		// this is the chance we don't log anything
 		return
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index e8b5d3ec..8efba5a9 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -388,6 +388,14 @@ func (deps *endpointDeps) overrideWithParams(httpRequest *http.Request, req *ope
 
 	setAmpExt(req.Site, "1")
 
+	account := httpRequest.FormValue("account")
+	if account != "" {
+		if req.Site.Publisher == nil {
+			req.Site.Publisher = &openrtb.Publisher{}
+		}
+		req.Site.Publisher.ID = account
+	}
+
 	slot := httpRequest.FormValue("slot")
 	if slot != "" {
 		req.Imp[0].TagID = slot
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 20acc2ae..86186fa8 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -39,6 +39,12 @@ import (
 
 const storedRequestTimeoutMillis = 50
 
+var (
+	dntKey      string = http.CanonicalHeaderKey("DNT")
+	dntDisabled int8   = 0
+	dntEnabled  int8   = 1
+)
+
 func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidator, requestsById stored_requests.Fetcher, categories stored_requests.CategoryFetcher, cfg *config.Configuration, met pbsmetrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, disabledBidders map[string]string, defReqJSON []byte, bidderMap map[string]openrtb_ext.BidderName) (httprouter.Handle, error) {
 
 	if ex == nil || validator == nil || requestsById == nil || cfg == nil || met == nil {
@@ -290,6 +296,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 		if err := validateBidAdjustmentFactors(bidExt.Prebid.BidAdjustmentFactors, aliases); err != nil {
 			return []error{err}
 		}
+
+		if err := validateSChains(bidExt); err != nil {
+			return []error{err}
+		}
 	}
 
 	if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) {
@@ -362,6 +372,11 @@ func validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases
 	return nil
 }
 
+func validateSChains(req *openrtb_ext.ExtRequest) error {
+	_, err := exchange.BidderToPrebidSChains(req)
+	return err
+}
+
 func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]string, index int) []error {
 	if imp.ID == "" {
 		return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)}
@@ -955,6 +970,8 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *ope
 func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidtor iputil.IPValidator) {
 	setIPImplicitly(httpReq, bidReq, ipValidtor)
 	setUAImplicitly(httpReq, bidReq)
+	setDoNotTrackImplicitly(httpReq, bidReq)
+
 }
 
 // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request,
@@ -1183,6 +1200,24 @@ func setUAImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) {
 	}
 }
 
+func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) {
+	if bidReq.Device == nil || bidReq.Device.DNT == nil {
+		dnt := httpReq.Header.Get(dntKey)
+		if dnt == "0" || dnt == "1" {
+			if bidReq.Device == nil {
+				bidReq.Device = &openrtb.Device{}
+			}
+
+			switch dnt {
+			case "0":
+				bidReq.Device.DNT = &dntDisabled
+			case "1":
+				bidReq.Device.DNT = &dntEnabled
+			}
+		}
+	}
+}
+
 // parseUserID gets this user's ID for the host machine, if it exists.
 func parseUserID(cfg *config.Configuration, httpReq *http.Request) (string, bool) {
 	if hostCookie, err := httpReq.Cookie(cfg.HostCookie.CookieName); hostCookie != nil && err == nil {
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json
new file mode 100644
index 00000000..b1fae20a
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/site-has-dnt.json
@@ -0,0 +1,45 @@
+{
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "device": {
+        "dnt": 1
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        }
+      }
+    }
+  }
+  
\ No newline at end of file
diff --git a/exchange/bidder.go b/exchange/bidder.go
index df9f0a3b..ee6a4942 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -312,6 +312,10 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
 // doRequest makes a request, handles the response, and returns the data needed by the
 // Bidder interface.
 func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData) *httpCallInfo {
+	return bidder.doRequestImpl(ctx, req, glog.Warningf)
+}
+
+func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg) *httpCallInfo {
 	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(req.Body))
 	if err != nil {
 		return &httpCallInfo{
@@ -325,12 +329,18 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 	if err != nil {
 		if err == context.DeadlineExceeded {
 			err = &errortypes.Timeout{Message: err.Error()}
-			if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); ok {
+			var corebidder adapters.Bidder = bidder.Bidder
+			// The bidder adapter normally stores an info-aware bidder (a bidder wrapper)
+			// rather than the actual bidder. So we need to unpack that first.
+			if b, ok := corebidder.(*adapters.InfoAwareBidder); ok {
+				corebidder = b.Bidder
+			}
+			if tb, ok := corebidder.(adapters.TimeoutBidder); ok {
 				// Toss the timeout notification call into a go routine, as we are out of time'
 				// and cannot delay processing. We don't do anything result, as there is not much
 				// we can do about a timeout notification failure. We do not want to get stuck in
 				// a loop of trying to report timeouts to the timeout notifications.
-				go bidder.doTimeoutNotification(tb, req)
+				go bidder.doTimeoutNotification(tb, req, logger)
 			}
 
 		}
@@ -366,7 +376,7 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 	}
 }
 
-func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData) {
+func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) {
 	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
 	defer cancel()
 	toReq, errL := timeoutBidder.MakeTimeoutNotification(req)
@@ -385,13 +395,13 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou
 					msg = fmt.Sprintf("TimeoutNotification: error:(%s) body:%s", err.Error(), string(toReq.Body))
 				}
 				// If logging is turned on, and logging is not disallowed via FailOnly
-				util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate)
+				util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate)
 			}
 		} else {
 			bidder.me.RecordTimeoutNotice(false)
 			if bidder.DebugConfig.TimeoutNotification.Log {
 				msg := fmt.Sprintf("TimeoutNotification: Failed to make timeout request: method(%s), uri(%s), error(%s)", toReq.Method, toReq.Uri, err.Error())
-				util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate)
+				util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate)
 			}
 		}
 	} else if bidder.DebugConfig.TimeoutNotification.Log {
@@ -402,7 +412,7 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou
 		} else {
 			msg = fmt.Sprintf("TimeoutNotification: Failed to generate timeout request: error(%s), bidder request marshal failed(%s)", errL[0].Error(), err.Error())
 		}
-		util.LogRandomSample(msg, glog.Warningf, bidder.DebugConfig.TimeoutNotification.SamplingRate)
+		util.LogRandomSample(msg, logger, bidder.DebugConfig.TimeoutNotification.SamplingRate)
 	}
 
 }
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 174a0b3e..3f0258dd 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -104,11 +104,10 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 
 	// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
 	blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels)
-	cleanRequests, aliases, cleanMetrics, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)
+	cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)
+
+	e.me.RecordRequestPrivacy(privacyLabels)
 
-	if cleanMetrics.gdprEnforced {
-		e.me.RecordTCFReq(pbsmetrics.TCFVersionToValue(cleanMetrics.gdprTcfVersion))
-	}
 	// List of bidders we have requests for.
 	liveAdapters := listBiddersWithRequests(cleanRequests)
 
diff --git a/exchange/utils.go b/exchange/utils.go
index 96c00ec0..bc1b555e 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -19,13 +19,27 @@ import (
 	"github.com/prebid/prebid-server/privacy/lmt"
 )
 
-// cleanMetrics is a struct to export any metrics data resulting from cleanOpenRTBRequests(). It starts with just
-// the TCF version, but made a struct to facilitate future expansion
-type cleanMetrics struct {
-	// A simple flag if GDPR is being enforced on this request.
-	gdprEnforced bool
-	// a zero value means a missing or invalid GDPR string
-	gdprTcfVersion int
+func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
+	bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
+
+	if len(req.Prebid.SChains) == 0 {
+		return bidderToSChains, nil
+	}
+
+	for _, schainWrapper := range req.Prebid.SChains {
+		if schainWrapper != nil && len(schainWrapper.Bidders) > 0 {
+			for _, bidder := range schainWrapper.Bidders {
+				if _, present := bidderToSChains[bidder]; present {
+					return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
+						"it must contain no more than one per bidder.", bidder)
+				} else {
+					bidderToSChains[bidder] = &schainWrapper.SChain
+				}
+			}
+		}
+	}
+
+	return bidderToSChains, nil
 }
 
 // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
@@ -40,7 +54,7 @@ func cleanOpenRTBRequests(ctx context.Context,
 	labels pbsmetrics.Labels,
 	gDPR gdpr.Permissions,
 	usersyncIfAmbiguous bool,
-	privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, cleanMetrics cleanMetrics, errs []error) {
+	privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, privacyLabels pbsmetrics.PrivacyLabels, errs []error) {
 
 	impsByBidder, errs := splitImps(orig.Imp)
 	if len(errs) > 0 {
@@ -75,13 +89,20 @@ func cleanOpenRTBRequests(ctx context.Context,
 		LMT:   lmtPolicy.ShouldEnforce(),
 	}
 
+	privacyLabels.CCPAProvided = ccpaPolicy.Value != ""
+	privacyLabels.CCPAEnforced = privacyEnforcement.CCPA
+	privacyLabels.COPPAEnforced = privacyEnforcement.COPPA
+	privacyLabels.LMTEnforced = privacyEnforcement.LMT
+
 	if gdpr == 1 {
-		cleanMetrics.gdprEnforced = true
+		privacyLabels.GDPREnforced = true
 		parsedConsent, err := vendorconsent.ParseString(consent)
 		if err == nil {
-			cleanMetrics.gdprTcfVersion = int(parsedConsent.Version())
+			version := int(parsedConsent.Version())
+			privacyLabels.GDPRTCFVersion = pbsmetrics.TCFVersionToValue(version)
 		}
 	}
+
 	// bidder level privacy policies
 	for bidder, bidReq := range requestsByBidder {
 
@@ -103,12 +124,35 @@ func cleanOpenRTBRequests(ctx context.Context,
 	return
 }
 
-func splitBidRequest(req *openrtb.BidRequest, impsByBidder map[string][]openrtb.Imp, aliases map[string]string, usersyncs IdFetcher, blabels map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels, labels pbsmetrics.Labels) (map[openrtb_ext.BidderName]*openrtb.BidRequest, []error) {
+func splitBidRequest(req *openrtb.BidRequest,
+	impsByBidder map[string][]openrtb.Imp,
+	aliases map[string]string,
+	usersyncs IdFetcher,
+	blabels map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels,
+	labels pbsmetrics.Labels) (map[openrtb_ext.BidderName]*openrtb.BidRequest, []error) {
+
 	requestsByBidder := make(map[openrtb_ext.BidderName]*openrtb.BidRequest, len(impsByBidder))
 	explicitBuyerUIDs, err := extractBuyerUIDs(req.User)
 	if err != nil {
 		return nil, []error{err}
 	}
+
+	var requestExt openrtb_ext.ExtRequest
+	var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
+	if len(req.Ext) > 0 {
+		err := json.Unmarshal(req.Ext, &requestExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+
+		sChainsByBidder, err = BidderToPrebidSChains(&requestExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+	} else {
+		sChainsByBidder = make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)
+	}
+
 	for bidder, imps := range impsByBidder {
 		reqCopy := *req
 		coreBidder := resolveBidder(bidder, aliases)
@@ -128,11 +172,57 @@ func splitBidRequest(req *openrtb.BidRequest, impsByBidder map[string][]openrtb.
 			blabels[coreBidder].CookieFlag = pbsmetrics.CookieFlagYes
 		}
 		reqCopy.Imp = imps
+
+		prepareSource(&reqCopy, bidder, sChainsByBidder)
+		prepareExt(&reqCopy, &requestExt)
+
 		requestsByBidder[openrtb_ext.BidderName(bidder)] = &reqCopy
 	}
 	return requestsByBidder, nil
 }
 
+func prepareExt(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) {
+	if len(req.Ext) == 0 {
+		return
+	}
+	extCopy := *unpackedExt
+	extCopy.Prebid.SChains = nil
+	reqExt, err := json.Marshal(extCopy)
+	if err == nil {
+		req.Ext = reqExt
+	}
+}
+
+func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) {
+	const sChainWildCard = "*"
+	var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain
+
+	wildCardSChain := sChainsByBidder[sChainWildCard]
+	bidderSChain := sChainsByBidder[bidder]
+
+	// source should not be modified
+	if bidderSChain == nil && wildCardSChain == nil {
+		return
+	}
+
+	if bidderSChain != nil {
+		selectedSChain = bidderSChain
+	} else {
+		selectedSChain = wildCardSChain
+	}
+
+	// set source
+	var source openrtb.Source
+	schain := openrtb_ext.ExtRequestPrebidSChain{
+		SChain: *selectedSChain,
+	}
+	sourceExt, err := json.Marshal(schain)
+	if err == nil {
+		source.Ext = sourceExt
+		req.Source = &source
+	}
+}
+
 // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext.
 // This prevents a Bidder from using these values to figure out who else is involved in the Auction.
 func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) {
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 25b5c881..86388f60 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -12,12 +12,43 @@ type ExtRequest struct {
 
 // ExtRequestPrebid defines the contract for bidrequest.ext.prebid
 type ExtRequestPrebid struct {
-	Aliases              map[string]string      `json:"aliases,omitempty"`
-	BidAdjustmentFactors map[string]float64     `json:"bidadjustmentfactors,omitempty"`
-	Cache                *ExtRequestPrebidCache `json:"cache,omitempty"`
-	StoredRequest        *ExtStoredRequest      `json:"storedrequest,omitempty"`
-	Targeting            *ExtRequestTargeting   `json:"targeting,omitempty"`
-	SupportDeals         bool                   `json:"supportdeals,omitempty"`
+	Aliases              map[string]string         `json:"aliases,omitempty"`
+	BidAdjustmentFactors map[string]float64        `json:"bidadjustmentfactors,omitempty"`
+	Cache                *ExtRequestPrebidCache    `json:"cache,omitempty"`
+	SChains              []*ExtRequestPrebidSChain `json:"schains,omitempty"`
+	StoredRequest        *ExtStoredRequest         `json:"storedrequest,omitempty"`
+	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
+	SupportDeals         bool                      `json:"supportdeals,omitempty"`
+}
+
+// ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains
+type ExtRequestPrebidSChain struct {
+	Bidders []string                     `json:"bidders,omitempty"`
+	SChain  ExtRequestPrebidSChainSChain `json:"schain"`
+}
+
+// ExtRequestPrebidSChainSChain defines the contract for bidrequest.ext.prebid.schains[i].schain
+type ExtRequestPrebidSChainSChain struct {
+	Complete int                                 `json:"complete"`
+	Nodes    []*ExtRequestPrebidSChainSChainNode `json:"nodes"`
+	Ver      string                              `json:"ver"`
+	Ext      json.RawMessage                     `json:"ext,omitempty"`
+}
+
+// ExtRequestPrebidSChainSChainNode defines the contract for bidrequest.ext.prebid.schains[i].schain[i].nodes
+type ExtRequestPrebidSChainSChainNode struct {
+	ASI    string          `json:"asi"`
+	SID    string          `json:"sid"`
+	RID    string          `json:"rid,omitempty"`
+	Name   string          `json:"name,omitempty"`
+	Domain string          `json:"domain,omitempty"`
+	HP     int             `json:"hp"`
+	Ext    json.RawMessage `json:"ext,omitempty"`
+}
+
+// SourceExt defines the contract for bidrequest.source.ext
+type SourceExt struct {
+	SChain ExtRequestPrebidSChainSChain `json:"schain"`
 }
 
 // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache
diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go
index 3d105dea..0dbe9a69 100644
--- a/pbsmetrics/config/metrics.go
+++ b/pbsmetrics/config/metrics.go
@@ -195,10 +195,10 @@ func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) {
 	}
 }
 
-// RecordTCFReq across all engines
-func (me *MultiMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
+// RecordRequestPrivacy across all engines
+func (me *MultiMetricsEngine) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) {
 	for _, thisME := range *me {
-		thisME.RecordTCFReq(version)
+		thisME.RecordRequestPrivacy(privacy)
 	}
 }
 
@@ -281,6 +281,6 @@ func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType p
 func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) {
 }
 
-// RecordReq as a noop
-func (me *DummyMetricsEngine) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
+// RecordRequestPrivacy as a noop
+func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) {
 }
diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go
index 73eb30a1..836434bf 100644
--- a/pbsmetrics/go_metrics.go
+++ b/pbsmetrics/go_metrics.go
@@ -53,7 +53,11 @@ type Metrics struct {
 	TimeoutNotificationFailure metrics.Meter
 
 	// TCF adaption metrics
-	TCFReqVersion map[TCFVersionValue]metrics.Meter
+	PrivacyCCPARequest       metrics.Meter
+	PrivacyCCPARequestOptOut metrics.Meter
+	PrivacyCOPPARequest      metrics.Meter
+	PrivacyLMTRequest        metrics.Meter
+	PrivacyTCFRequestVersion map[TCFVersionValue]metrics.Meter
 
 	AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics
 	// Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically
@@ -141,7 +145,11 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		TimeoutNotificationSuccess: blankMeter,
 		TimeoutNotificationFailure: blankMeter,
 
-		TCFReqVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())),
+		PrivacyCCPARequest:       blankMeter,
+		PrivacyCCPARequestOptOut: blankMeter,
+		PrivacyCOPPARequest:      blankMeter,
+		PrivacyLMTRequest:        blankMeter,
+		PrivacyTCFRequestVersion: make(map[TCFVersionValue]metrics.Meter, len(TCFVersions())),
 
 		AdapterMetrics:  make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)),
 		accountMetrics:  make(map[string]*accountMetrics),
@@ -149,6 +157,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 
 		exchanges: exchanges,
 	}
+
 	for _, a := range exchanges {
 		newMetrics.AdapterMetrics[a] = makeBlankAdapterMetrics()
 	}
@@ -166,7 +175,7 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 	}
 
 	for _, v := range TCFVersions() {
-		newMetrics.TCFReqVersion[v] = blankMeter
+		newMetrics.PrivacyTCFRequestVersion[v] = blankMeter
 	}
 
 	//to minimize memory usage, queuedTimeout metric is now supported for video endpoint only
@@ -234,8 +243,12 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 	newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry)
 	newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry)
 
+	newMetrics.PrivacyCCPARequest = metrics.GetOrRegisterMeter("privacy.request.ccpa.specified", registry)
+	newMetrics.PrivacyCCPARequestOptOut = metrics.GetOrRegisterMeter("privacy.request.ccpa.opt-out", registry)
+	newMetrics.PrivacyCOPPARequest = metrics.GetOrRegisterMeter("privacy.request.coppa", registry)
+	newMetrics.PrivacyLMTRequest = metrics.GetOrRegisterMeter("privacy.request.lmt", registry)
 	for _, version := range TCFVersions() {
-		newMetrics.TCFReqVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry)
+		newMetrics.PrivacyTCFRequestVersion[version] = metrics.GetOrRegisterMeter(fmt.Sprintf("privacy.request.tcf.%s", string(version)), registry)
 	}
 
 	return newMetrics
@@ -582,12 +595,28 @@ func (me *Metrics) RecordTimeoutNotice(success bool) {
 	return
 }
 
-func (me *Metrics) RecordTCFReq(version TCFVersionValue) {
-	met, ok := me.TCFReqVersion[version]
-	if ok {
-		met.Mark(1)
-	} else {
-		me.TCFReqVersion[TCFVersionErr].Mark(1)
+func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) {
+	if privacy.CCPAProvided {
+		me.PrivacyCCPARequest.Mark(1)
+		if privacy.CCPAEnforced {
+			me.PrivacyCCPARequestOptOut.Mark(1)
+		}
+	}
+
+	if privacy.COPPAEnforced {
+		me.PrivacyCOPPARequest.Mark(1)
+	}
+
+	if privacy.GDPREnforced {
+		if metric, ok := me.PrivacyTCFRequestVersion[privacy.GDPRTCFVersion]; ok {
+			metric.Mark(1)
+		} else {
+			me.PrivacyTCFRequestVersion[TCFVersionErr].Mark(1)
+		}
+	}
+
+	if privacy.LMTEnforced {
+		me.PrivacyLMTRequest.Mark(1)
 	}
 	return
 }
diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go
index 0e94fe71..514fbac1 100644
--- a/pbsmetrics/metrics.go
+++ b/pbsmetrics/metrics.go
@@ -41,6 +41,16 @@ type RequestLabels struct {
 	RequestStatus RequestStatus
 }
 
+// PrivacyLabels defines metrics describing the result of privacy enforcement.
+type PrivacyLabels struct {
+	CCPAEnforced   bool
+	CCPAProvided   bool
+	COPPAEnforced  bool
+	GDPREnforced   bool
+	GDPRTCFVersion TCFVersionValue
+	LMTEnforced    bool
+}
+
 // Label typecasting. Se below the type definitions for possible values
 
 // DemandSource : Demand source enumeration
@@ -257,7 +267,7 @@ const (
 	TCFVersionV2  TCFVersionValue = "v2"
 )
 
-// TCFVersions rtuens the possible values for the TCF version
+// TCFVersions returns the possible values for the TCF version
 func TCFVersions() []TCFVersionValue {
 	return []TCFVersionValue{
 		TCFVersionErr,
@@ -305,5 +315,5 @@ type MetricsEngine interface {
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
 	RecordTimeoutNotice(sucess bool)
-	RecordTCFReq(version TCFVersionValue)
+	RecordRequestPrivacy(privacy PrivacyLabels)
 }
diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go
index a6d36a72..6c263f0a 100644
--- a/pbsmetrics/metrics_mock.go
+++ b/pbsmetrics/metrics_mock.go
@@ -107,7 +107,7 @@ func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) {
 	me.Called(success)
 }
 
-// RecordTCFReq mock
-func (me *MetricsEngineMock) RecordTCFReq(version TCFVersionValue) {
-	me.Called(version)
+// RecordRequestPrivacy mock
+func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) {
+	me.Called(privacy)
 }
diff --git a/pbsmetrics/prometheus/preload.go b/pbsmetrics/prometheus/preload.go
index 19f4f225..ef1d300c 100644
--- a/pbsmetrics/prometheus/preload.go
+++ b/pbsmetrics/prometheus/preload.go
@@ -8,15 +8,16 @@ import (
 func preloadLabelValues(m *Metrics) {
 	var (
 		actionValues          = actionsAsString()
-		adapterValues         = adaptersAsString()
 		adapterErrorValues    = adapterErrorsAsString()
+		adapterValues         = adaptersAsString()
 		bidTypeValues         = []string{markupDeliveryAdm, markupDeliveryNurl}
 		boolValues            = boolValuesAsString()
 		cacheResultValues     = cacheResultsAsString()
-		cookieValues          = cookieTypesAsString()
 		connectionErrorValues = []string{connectionAcceptError, connectionCloseError}
+		cookieValues          = cookieTypesAsString()
 		requestStatusValues   = requestStatusesAsString()
 		requestTypeValues     = requestTypesAsString()
+		sourceValues          = []string{sourceRequest}
 	)
 
 	preloadLabelValuesForCounter(m.connectionsError, map[string][]string{
@@ -100,9 +101,22 @@ func preloadLabelValues(m *Metrics) {
 		requestStatusLabel: {requestSuccessLabel, requestRejectLabel},
 	})
 
-	preloadLabelValuesForCounter(m.tcfVersion, map[string][]string{
+	preloadLabelValuesForCounter(m.privacyCCPA, map[string][]string{
+		sourceLabel: sourceValues,
+		optOutLabel: boolValues,
+	})
+
+	preloadLabelValuesForCounter(m.privacyCOPPA, map[string][]string{
+		sourceLabel: sourceValues,
+	})
+
+	preloadLabelValuesForCounter(m.privacyLMT, map[string][]string{
+		sourceLabel: sourceValues,
+	})
+
+	preloadLabelValuesForCounter(m.privacyTCF, map[string][]string{
+		sourceLabel:  sourceValues,
 		versionLabel: tcfVersionsAsString(),
-		sourceLabel:  {string(sourceRequest)},
 	})
 }
 
diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go
index bf854746..d94c4d78 100644
--- a/pbsmetrics/prometheus/prometheus.go
+++ b/pbsmetrics/prometheus/prometheus.go
@@ -29,7 +29,10 @@ type Metrics struct {
 	storedImpressionsCacheResult *prometheus.CounterVec
 	storedRequestCacheResult     *prometheus.CounterVec
 	timeoutNotifications         *prometheus.CounterVec
-	tcfVersion                   *prometheus.CounterVec
+	privacyCCPA                  *prometheus.CounterVec
+	privacyCOPPA                 *prometheus.CounterVec
+	privacyLMT                   *prometheus.CounterVec
+	privacyTCF                   *prometheus.CounterVec
 
 	// Adapter Metrics
 	adapterBids          *prometheus.CounterVec
@@ -60,6 +63,7 @@ const (
 	isNativeLabel        = "native"
 	isVideoLabel         = "video"
 	markupDeliveryLabel  = "delivery"
+	optOutLabel          = "opt_out"
 	privacyBlockedLabel  = "privacy_blocked"
 	requestStatusLabel   = "request_status"
 	requestTypeLabel     = "request_type"
@@ -165,11 +169,26 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of timeout notifications triggered, and if they were successfully sent.",
 		[]string{successLabel})
 
-	metrics.tcfVersion = newCounter(cfg, metrics.Registry,
+	metrics.privacyCCPA = newCounter(cfg, metrics.Registry,
+		"privacy_ccpa",
+		"Count of total requests to Prebid Server where CCPA was provided by source and opt-out .",
+		[]string{sourceLabel, optOutLabel})
+
+	metrics.privacyCOPPA = newCounter(cfg, metrics.Registry,
+		"privacy_coppa",
+		"Count of total requests to Prebid Server where the COPPA flag was set by source",
+		[]string{sourceLabel})
+
+	metrics.privacyTCF = newCounter(cfg, metrics.Registry,
 		"privacy_tcf",
-		"Count of TCF versions for requests where GDPR was enforced.",
+		"Count of TCF versions for requests where GDPR was enforced by source and version.",
 		[]string{versionLabel, sourceLabel})
 
+	metrics.privacyLMT = newCounter(cfg, metrics.Registry,
+		"privacy_lmt",
+		"Count of total requests to Prebid Server where the LMT flag was set by source",
+		[]string{sourceLabel})
+
 	metrics.adapterBids = newCounter(cfg, metrics.Registry,
 		"adapter_bids",
 		"Count of bids labeled by adapter and markup delivery type (adm or nurl).",
@@ -434,9 +453,30 @@ func (m *Metrics) RecordTimeoutNotice(success bool) {
 	}
 }
 
-func (m *Metrics) RecordTCFReq(version pbsmetrics.TCFVersionValue) {
-	m.tcfVersion.With(prometheus.Labels{
-		versionLabel: string(version),
-		sourceLabel:  sourceRequest,
-	}).Inc()
+func (m *Metrics) RecordRequestPrivacy(privacy pbsmetrics.PrivacyLabels) {
+	if privacy.CCPAProvided {
+		m.privacyCCPA.With(prometheus.Labels{
+			sourceLabel: sourceRequest,
+			optOutLabel: strconv.FormatBool(privacy.CCPAEnforced),
+		}).Inc()
+	}
+
+	if privacy.COPPAEnforced {
+		m.privacyCOPPA.With(prometheus.Labels{
+			sourceLabel: sourceRequest,
+		}).Inc()
+	}
+
+	if privacy.GDPREnforced {
+		m.privacyTCF.With(prometheus.Labels{
+			versionLabel: string(privacy.GDPRTCFVersion),
+			sourceLabel:  sourceRequest,
+		}).Inc()
+	}
+
+	if privacy.LMTEnforced {
+		m.privacyLMT.With(prometheus.Labels{
+			sourceLabel: sourceRequest,
+		}).Inc()
+	}
 }
diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml
index fe791343..7a20d52b 100644
--- a/static/bidder-info/adtelligent.yaml
+++ b/static/bidder-info/adtelligent.yaml
@@ -4,6 +4,7 @@ capabilities:
   app:
     mediaTypes:
       - banner
+      - video
   site:
     mediaTypes:
       - banner
diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml
index 56230bf3..324e5c6d 100644
--- a/static/bidder-info/audienceNetwork.yaml
+++ b/static/bidder-info/audienceNetwork.yaml
@@ -1,11 +1,6 @@
 maintainer:
   email: "none"
 capabilities:
-  site:
-    mediaTypes:
-      - banner
-      - video
-      - native
   app:
     mediaTypes:
       - banner


Test patch:
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json
deleted file mode 100644
index 01bab3df..00000000
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/banner-site.json
+++ /dev/null
@@ -1,132 +0,0 @@
-{
-    "mockBidRequest": {
-        "id": "test-req-id",
-        "imp": [
-            {
-                "id": "test-imp-id",
-                "banner": {
-                    "format": [
-                        {
-                            "w": 300,
-                            "h": 250
-                        }
-                    ],
-                    "w": 300,
-                    "h": 250
-                },
-                "ext": {
-                    "bidder": {
-                        "publisherid": "123",
-                        "placementid": "456"
-                    }
-                }
-            }
-        ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
-        },
-        "device": {
-            "ip": "152.193.6.74"
-        },
-        "user": {
-            "id": "db089de9-a62e-4861-a881-0ff15e052516",
-            "buyeruid": "v4_bidder_token"
-        },
-        "tmax": 500
-    },
-    "httpcalls": [
-        {
-            "expectedRequest": {
-                "uri": "https://an.facebook.com/placementbid.ortb",
-                "headers": {
-                    "Accept": [
-                        "application/json"
-                    ],
-                    "Content-Type": [
-                        "application/json;charset=utf-8"
-                    ],
-                    "X-Fb-Pool-Routing-Token": [
-                        "v4_bidder_token"
-                    ]
-                },
-                "body": {
-                    "id": "test-imp-id",
-                    "imp": [
-                        {
-                            "id": "test-imp-id",
-                            "banner": {
-                                "w": -1,
-                                "h": 250
-                            },
-                            "tagid": "123_456"
-                        }
-                    ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
-                        "publisher": {
-                            "id": "123"
-                        }
-                    },
-                    "device": {
-                        "ip": "152.193.6.74"
-                    },
-                    "user": {
-                        "id": "db089de9-a62e-4861-a881-0ff15e052516",
-                        "buyeruid": "v4_bidder_token"
-                    },
-                    "tmax": 500,
-                    "ext": {
-                        "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174",
-                        "platformid": "test-platform-id"
-                    }
-                }
-            },
-            "mockResponse": {
-                "status": 200,
-                "body": {
-                    "id": "test-imp-id",
-                    "seatbid": [
-                        {
-                            "bid": [
-                                {
-                                    "id": "987",
-                                    "impid": "test-imp-id",
-                                    "price": 1.000000,
-                                    "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}",
-                                    "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                                    "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                                    "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}"
-                                }
-                            ]
-                        }
-                    ],
-                    "bidid": "654",
-                    "cur": "USD"
-                }
-            }
-        }
-    ],
-    "expectedBidResponses": [
-        {
-            "currency": "USD",
-            "bids": [
-                {
-                    "bid": {
-                        "id": "987",
-                        "impid": "test-imp-id",
-                        "price": 1,
-                        "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}",
-                        "adid": "987",
-                        "crid": "987",
-                        "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                        "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0",
-                        "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}"
-                    },
-                    "type": "banner"
-                }
-            ]
-        }
-    ]
-}
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
index 9f563f11..573032c8 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/interstitial.json
@@ -23,9 +23,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -64,9 +64,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
index 16bed344..08639bee 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/native-1.1.json
@@ -16,9 +16,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -56,9 +56,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
index 5ece0f08..35bdf9a4 100644
--- a/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
+++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video.json
@@ -21,9 +21,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -66,9 +66,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
index 5469fefb..450e0d9e 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/banner-format-only.json
@@ -24,9 +24,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -64,9 +64,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json
index f145f5fe..c33807bd 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-adm.json
@@ -18,9 +18,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -55,9 +55,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json
index fa9fd913..b229d41a 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-banner-height.json
@@ -22,9 +22,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json
index ad19d94c..68ca8044 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json
@@ -20,9 +20,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json
index b57c9001..50212155 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm-bidid.json
@@ -18,9 +18,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -55,9 +55,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json
index 23227aab..832b16dc 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-adm.json
@@ -18,9 +18,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -55,9 +55,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json
index 016e8de0..0793f990 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/missing-banner-height.json
@@ -20,9 +20,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
index 231c2826..682c33e4 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/multi-imp.json
@@ -41,9 +41,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -81,9 +81,9 @@
                             "tagid": "pub1_plmt1"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "pub1"
                         }
@@ -152,9 +152,9 @@
                             "tagid": "pub2_plmt2"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "pub2"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
index 45b35e05..642e4958 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json
@@ -16,9 +16,9 @@
                 }
             }
         ],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -56,9 +56,9 @@
                             "tagid": "123_456"
                         }
                     ],
-                    "site": {
-                        "domain": "prebid.org",
-                        "page": "prebid.org",
+                    "app": {
+                        "id": "app-abc",
+                        "bundle": "com.prebid",
                         "publisher": {
                             "id": "123"
                         }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json
index 7420f7e8..fccdf71c 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-imps.json
@@ -2,9 +2,9 @@
     "mockBidRequest": {
         "id": "test-req-id",
         "imp": [],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json
index 964dcb48..72b4fbac 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-buyeruid.json
@@ -26,9 +26,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json
index a9c3c23d..f13b70e1 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-placementId.json
@@ -25,9 +25,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json
index c50f3d36..a80a1e09 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/required-param-publisherId.json
@@ -25,9 +25,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json
index 7ff88861..f0a11905 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/server-error-500.json
@@ -14,9 +14,9 @@
                 }
             }
         }],
-        "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org"
+        "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid"
         },
         "device": {
             "ip": "152.193.6.74"
@@ -51,9 +51,9 @@
                     },
                     "tagid": "123_456"
                 }],
-                "site": {
-                    "domain": "prebid.org",
-                    "page": "prebid.org",
+                "app": {
+                    "id": "app-abc",
+                    "bundle": "com.prebid",
                     "publisher": {
                         "id": "123"
                     }
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json
new file mode 100644
index 00000000..9155352a
--- /dev/null
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/site-not-supported.json
@@ -0,0 +1,38 @@
+{
+    "mockBidRequest": {
+        "id": "test-req-id",
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }],
+                "w": 300,
+                "h": 250
+            },
+            "ext": {
+                "bidder": {
+                    "publisherid": "123",
+                    "placementid": "456"
+                }
+            }
+        }],
+        "site": {
+            "domain": "prebid.org",
+            "page": "prebid.org"
+        },
+        "device": {
+            "ip": "152.193.6.74"
+        },
+        "user": {
+            "id": "db089de9-a62e-4861-a881-0ff15e052516",
+            "buyeruid": "v4_bidder_token"
+        },
+        "tmax": 500
+    },
+    "expectedMakeRequestsErrors": [{
+        "value": "Site impressions are not supported.",
+        "comparison": "literal"
+    }]
+}
\ No newline at end of file
diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
index 34c1eccc..45c34192 100644
--- a/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
+++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/split-placementId.json
@@ -21,9 +21,9 @@
         }
       }
     ],
-    "site": {
-      "domain": "prebid.org",
-      "page": "prebid.org"
+    "app": {
+      "id": "app-abc",
+      "bundle": "com.prebid"
     },
     "device": {
       "ip": "152.193.6.74"
@@ -50,9 +50,9 @@
               "tagid": "123_456"
             }
           ],
-          "site": {
-            "domain": "prebid.org",
-            "page": "prebid.org",
+          "app": {
+            "id": "app-abc",
+            "bundle": "com.prebid",
             "publisher": {
                 "id": "123"
             }
diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go
index 7f567d61..912f1222 100644
--- a/adapters/audienceNetwork/facebook_test.go
+++ b/adapters/audienceNetwork/facebook_test.go
@@ -61,23 +61,6 @@ func TestMakeTimeoutNoticeApp(t *testing.T) {
 	assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.")
 }
 
-func TestMakeTimeoutNoticeSite(t *testing.T) {
-	req := adapters.RequestData{
-		Body: []byte(`{"id":"1234","imp":[{"id":"1234"}],"site":{"publisher":{"id":"5678"}}}`),
-	}
-	fba := NewFacebookBidder("test-platform-id", "test-app-secret")
-
-	tb, ok := fba.(adapters.TimeoutBidder)
-	if !ok {
-		t.Error("Facebook adapter is not a TimeoutAdapter")
-	}
-
-	toReq, err := tb.MakeTimeoutNotification(&req)
-	assert.Nil(t, err, "Facebook MakeTimeoutNotification() return an error %v", err)
-	expectedUri := "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=5678&auction=1234&ortb_loss_code=2"
-	assert.Equal(t, expectedUri, toReq.Uri, "Facebook timeout notification not returning the expected URI.")
-}
-
 func TestMakeTimeoutNoticeBadRequest(t *testing.T) {
 	req := adapters.RequestData{
 		Body: []byte(`{"imp":[{{"id":"1234"}}`),
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 731fd55e..692d3fb0 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -755,8 +755,9 @@ func TestQueryParamOverrides(t *testing.T) {
 	curl := "http://example.com"
 	slot := "1234"
 	timeout := int64(500)
+	account := "12345"
 
-	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d", requestID, curl, slot, timeout), nil)
+	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d&account=%s", requestID, curl, slot, timeout, account), nil)
 	recorder := httptest.NewRecorder()
 	endpoint(recorder, request, nil)
 
@@ -784,6 +785,10 @@ func TestQueryParamOverrides(t *testing.T) {
 	if resolvedRequest.Site == nil || resolvedRequest.Site.Page != curl {
 		t.Errorf("Expected Site.Page to equal curl (%s), got: %s", curl, resolvedRequest.Site.Page)
 	}
+
+	if resolvedRequest.Site == nil || resolvedRequest.Site.Publisher == nil || resolvedRequest.Site.Publisher.ID != account {
+		t.Errorf("Expected Site.Publisher.ID to equal (%s), got: %s", account, resolvedRequest.Site.Publisher.ID)
+	}
 }
 
 func TestOverrideDimensions(t *testing.T) {
@@ -876,6 +881,7 @@ type formatOverrideSpec struct {
 	overrideWidth  uint64
 	overrideHeight uint64
 	multisize      string
+	account        string
 	expect         []openrtb.Format
 }
 
@@ -897,7 +903,7 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 		openrtb_ext.BidderMap,
 	)
 
-	url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize)
+	url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s&account=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize, s.account)
 	request := httptest.NewRequest("GET", url, nil)
 	recorder := httptest.NewRecorder()
 	endpoint(recorder, request, nil)
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 97f0038a..957760c6 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -604,6 +604,189 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 	}
 }
 
+func TestImplicitDNT(t *testing.T) {
+	var (
+		disabled int8 = 0
+		enabled  int8 = 1
+	)
+	testCases := []struct {
+		description     string
+		dntHeader       string
+		request         openrtb.BidRequest
+		expectedRequest openrtb.BidRequest
+	}{
+		{
+			description:     "Device Missing - Not Set In Header",
+			dntHeader:       "",
+			request:         openrtb.BidRequest{},
+			expectedRequest: openrtb.BidRequest{},
+		},
+		{
+			description: "Device Missing - Set To 0 In Header",
+			dntHeader:   "0",
+			request:     openrtb.BidRequest{},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &disabled,
+				},
+			},
+		},
+		{
+			description: "Device Missing - Set To 1 In Header",
+			dntHeader:   "1",
+			request:     openrtb.BidRequest{},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Not Set In Request - Not Set In Header",
+			dntHeader:   "",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+		},
+		{
+			description: "Not Set In Request - Set To 0 In Header",
+			dntHeader:   "0",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &disabled,
+				},
+			},
+		},
+		{
+			description: "Not Set In Request - Set To 1 In Header",
+			dntHeader:   "1",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Set In Request - Not Set In Header",
+			dntHeader:   "",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Set In Request - Set To 0 In Header",
+			dntHeader:   "0",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+		{
+			description: "Set In Request - Set To 1 In Header",
+			dntHeader:   "1",
+			request: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+			expectedRequest: openrtb.BidRequest{
+				Device: &openrtb.Device{
+					DNT: &enabled,
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", nil)
+		httpReq.Header.Set("DNT", test.dntHeader)
+		setDoNotTrackImplicitly(httpReq, &test.request)
+		assert.Equal(t, test.expectedRequest, test.request)
+	}
+}
+
+func TestImplicitDNTEndToEnd(t *testing.T) {
+	var (
+		disabled int8 = 0
+		enabled  int8 = 1
+	)
+	testCases := []struct {
+		description string
+		reqJSONFile string
+		dntHeader   string
+		expectedDNT *int8
+	}{
+		{
+			description: "Not Set In Request - Not Set In Header",
+			reqJSONFile: "site.json",
+			dntHeader:   "",
+			expectedDNT: nil,
+		},
+		{
+			description: "Not Set In Request - Set To 0 In Header",
+			reqJSONFile: "site.json",
+			dntHeader:   "0",
+			expectedDNT: &disabled,
+		},
+		{
+			description: "Not Set In Request - Set To 1 In Header",
+			reqJSONFile: "site.json",
+			dntHeader:   "1",
+			expectedDNT: &enabled,
+		},
+		{
+			description: "Set In Request - Not Set In Header",
+			reqJSONFile: "site-has-dnt.json",
+			dntHeader:   "",
+			expectedDNT: &enabled, // Hardcoded value in test file.
+		},
+		{
+			description: "Set In Request - Not Overwritten By Header",
+			reqJSONFile: "site-has-dnt.json",
+			dntHeader:   "0",
+			expectedDNT: &enabled, // Hardcoded value in test file.
+		},
+	}
+
+	metrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
+	for _, test := range testCases {
+		exchange := &nobidExchange{}
+		endpoint, _ := NewEndpoint(exchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, metrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, []byte{}, openrtb_ext.BidderMap)
+
+		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
+		httpReq.Header.Set("DNT", test.dntHeader)
+
+		endpoint(httptest.NewRecorder(), httpReq, nil)
+
+		result := exchange.gotRequest
+		if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") {
+			t.FailNow()
+		}
+		assert.Equal(t, test.expectedDNT, result.Device.DNT, test.description+":dnt")
+	}
+}
 func TestImplicitSecure(t *testing.T) {
 	httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
 	httpReq.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https")
@@ -1039,6 +1222,53 @@ func TestCCPAInvalid(t *testing.T) {
 	assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request")
 }
 
+func TestSChainInvalid(t *testing.T) {
+	deps := &endpointDeps{
+		&nobidExchange{},
+		newParamsValidator(t),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		&config.Configuration{},
+		pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}),
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BidderMap,
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+	}
+
+	ui := uint64(1)
+	req := openrtb.BidRequest{
+		ID: "someID",
+		Imp: []openrtb.Imp{
+			{
+				ID: "imp-ID",
+				Banner: &openrtb.Banner{
+					W: &ui,
+					H: &ui,
+				},
+				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
+			},
+		},
+		Site: &openrtb.Site{
+			ID: "myID",
+		},
+		Regs: &openrtb.Regs{
+			Ext: json.RawMessage(`{"us_privacy":"abcd"}`),
+		},
+		Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
+	}
+
+	errL := deps.validateRequest(&req)
+
+	expectedError := fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
+	assert.ElementsMatch(t, errL, []error{expectedError})
+}
+
 func TestSanitizeRequest(t *testing.T) {
 	testCases := []struct {
 		description  string
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index b776715a..d4fc0cf7 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -1,6 +1,7 @@
 package exchange
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
@@ -10,6 +11,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/golang/glog"
 	"github.com/mxmCherry/openrtb"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
@@ -1237,8 +1239,8 @@ func TestTimeoutNotificationOff(t *testing.T) {
 	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
 	defer server.Close()
 
-	bidderImpl := &notifingBidder{
-		notiRequest: adapters.RequestData{
+	bidderImpl := &notifyingBidder{
+		notifyRequest: adapters.RequestData{
 			Method:  "GET",
 			Uri:     server.URL + "/notify/me",
 			Body:    nil,
@@ -1254,39 +1256,83 @@ func TestTimeoutNotificationOff(t *testing.T) {
 	if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok {
 		t.Error("Failed to cast bidder to a TimeoutBidder")
 	} else {
-		bidder.doTimeoutNotification(tb, &adapters.RequestData{})
+		bidder.doTimeoutNotification(tb, &adapters.RequestData{}, glog.Warningf)
 	}
 }
 
 func TestTimeoutNotificationOn(t *testing.T) {
-	respBody := "{\"bid\":false}"
-	respStatus := 200
-	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
+	// Expire context immediately to force timeout handler.
+	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now())
+	cancelFunc()
+
+	// Notification logic is hardcoded for 200ms. We need to wait for a little longer than that.
+	server := httptest.NewServer(mockSlowHandler(205*time.Millisecond, 200, `{"bid":false}`))
 	defer server.Close()
 
-	bidderImpl := &notifingBidder{
-		notiRequest: adapters.RequestData{
+	bidder := &notifyingBidder{
+		notifyRequest: adapters.RequestData{
 			Method:  "GET",
 			Uri:     server.URL + "/notify/me",
 			Body:    nil,
 			Headers: http.Header{},
 		},
 	}
-	bidder := &bidderAdapter{
-		Bidder: bidderImpl,
+
+	// Wrap with BidderInfo to mimic exchange.go flow.
+	bidderWrappedWithInfo := wrapWithBidderInfo(bidder)
+
+	bidderAdapter := &bidderAdapter{
+		Bidder: bidderWrappedWithInfo,
 		Client: server.Client(),
 		DebugConfig: config.Debug{
 			TimeoutNotification: config.TimeoutNotification{
-				Log: true,
+				Log:          true,
+				SamplingRate: 1.0,
 			},
 		},
 		me: &metricsConfig.DummyMetricsEngine{},
 	}
-	if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok {
-		t.Error("Failed to cast bidder to a TimeoutBidder")
-	} else {
-		bidder.doTimeoutNotification(tb, &adapters.RequestData{})
+
+	// Unwrap To Mimic exchange.go Casting Code
+	var coreBidder adapters.Bidder = bidderAdapter.Bidder
+	if b, ok := coreBidder.(*adapters.InfoAwareBidder); ok {
+		coreBidder = b.Bidder
+	}
+	if _, ok := coreBidder.(adapters.TimeoutBidder); !ok {
+		t.Fatal("Failed to cast bidder to a TimeoutBidder")
+	}
+
+	bidRequest := adapters.RequestData{
+		Method: "POST",
+		Uri:    server.URL,
+		Body:   []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`),
+	}
+
+	var loggerBuffer bytes.Buffer
+	logger := func(msg string, args ...interface{}) {
+		loggerBuffer.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...))
+	}
+
+	bidderAdapter.doRequestImpl(ctx, &bidRequest, logger)
+
+	// Wait a little longer than the 205ms mock server sleep.
+	time.Sleep(210 * time.Millisecond)
+
+	logExpected := "TimeoutNotification: error:(context deadline exceeded) body:\n"
+	logActual := loggerBuffer.String()
+	assert.EqualValues(t, logExpected, logActual)
+}
+
+func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder {
+	bidderInfo := adapters.BidderInfo{
+		Status: adapters.StatusActive,
+		Capabilities: &adapters.CapabilitiesInfo{
+			App: &adapters.PlatformInfo{
+				MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner},
+			},
+		},
 	}
+	return adapters.EnforceBidderInfo(bidder, bidderInfo)
 }
 
 type goodSingleBidder struct {
@@ -1363,18 +1409,19 @@ func (bidder *bidRejector) MakeBids(internalRequest *openrtb.BidRequest, externa
 	return nil, []error{errors.New("Can't make a response.")}
 }
 
-type notifingBidder struct {
-	notiRequest adapters.RequestData
+type notifyingBidder struct {
+	requests      []*adapters.RequestData
+	notifyRequest adapters.RequestData
 }
 
-func (bidder *notifingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	return nil, nil
+func (bidder *notifyingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	return bidder.requests, nil
 }
 
-func (bidder *notifingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
 	return nil, nil
 }
 
-func (bidder *notifingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
-	return &bidder.notiRequest, nil
+func (bidder *notifyingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
+	return &bidder.notifyRequest, nil
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 161b24fd..96f740de 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -1830,6 +1830,15 @@ func mockHandler(statusCode int, getBody string, postBody string) http.Handler {
 	})
 }
 
+func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		time.Sleep(delay)
+
+		w.WriteHeader(statusCode)
+		w.Write([]byte(body))
+	})
+}
+
 type wellBehavedCache struct{}
 
 func (c *wellBehavedCache) GetExtCacheData() (string, string) {
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index e50d0f77..608e6a17 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -15,7 +15,9 @@ import (
 // permissionsMock mocks the Permissions interface for tests
 //
 // It only allows appnexus for GDPR consent
-type permissionsMock struct{}
+type permissionsMock struct {
+	personalInfoAllowed bool
+}
 
 func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
 	return true, nil
@@ -26,10 +28,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 }
 
 func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
-	if bidder == "appnexus" {
-		return true, true, nil
-	}
-	return false, false, nil
+	return p.personalInfoAllowed, p.personalInfoAllowed, nil
 }
 
 func (p *permissionsMock) AMPException() bool {
@@ -80,7 +79,7 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
+		reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
 		if test.hasError {
 			assert.NotNil(t, err, "Error shouldn't be nil")
 		} else {
@@ -92,26 +91,48 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 
 func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 	testCases := []struct {
-		description     string
-		enforceCCPA     bool
-		expectDataScrub bool
+		description         string
+		ccpaConsent         string
+		enforceCCPA         bool
+		expectDataScrub     bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
 	}{
 		{
-			description:     "Feature Flag Enabled",
+			description:     "Feature Flag Enabled - Opt Out",
+			ccpaConsent:     "1-Y-",
 			enforceCCPA:     true,
 			expectDataScrub: true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: true,
+			},
+		},
+		{
+			description:     "Feature Flag Enabled - Opt In",
+			ccpaConsent:     "1-N-",
+			enforceCCPA:     true,
+			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: true,
+				CCPAEnforced: false,
+			},
 		},
 		{
 			description:     "Feature Flag Disabled",
+			ccpaConsent:     "1-Y-",
 			enforceCCPA:     false,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				CCPAProvided: false,
+				CCPAEnforced: false,
+			},
 		},
 	}
 
 	for _, test := range testCases {
 		req := newBidRequest(t)
 		req.Regs = &openrtb.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"1-Y-"}`),
+			Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`),
 		}
 
 		privacyConfig := config.Privacy{
@@ -120,11 +141,10 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			},
 		}
 
-		results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
 		result := results["appnexus"]
 
 		assert.Nil(t, errs)
-
 		if test.expectDataScrub {
 			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
@@ -132,6 +152,137 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
 		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
+	}
+}
+
+func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
+	testCases := []struct {
+		description         string
+		coppa               int8
+		expectDataScrub     bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
+	}{
+		{
+			description:     "Enabled",
+			coppa:           1,
+			expectDataScrub: true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				COPPAEnforced: true,
+			},
+		},
+		{
+			description:     "Disabled",
+			coppa:           0,
+			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				COPPAEnforced: false,
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequest(t)
+		req.Regs = &openrtb.Regs{COPPA: test.coppa}
+
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{})
+		result := results["appnexus"]
+
+		assert.Nil(t, errs)
+		if test.expectDataScrub {
+			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.Equal(t, result.User.Yob, int64(0), test.description+":User.Yob")
+		} else {
+			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.NotEqual(t, result.User.Yob, int64(0), test.description+":User.Yob")
+		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
+	}
+}
+
+func TestCleanOpenRTBRequestsSChain(t *testing.T) {
+	testCases := []struct {
+		description  string
+		inSourceExt  json.RawMessage
+		inExt        json.RawMessage
+		outSourceExt json.RawMessage
+		outExt       json.RawMessage
+		hasError     bool
+	}{
+		{
+			description:  "Empty root ext and source ext",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(``),
+			outSourceExt: json.RawMessage(``),
+			outExt:       json.RawMessage(``),
+			hasError:     false,
+		},
+		{
+			description:  "No schains in root ext and empty source ext",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[]}}`),
+			outSourceExt: json.RawMessage(``),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use source schain -- no bidder schain or wildcard schain in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["bidder1"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use schain for bidder in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use wildcard schain in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use schain for bidder in ext.prebid.schains instead of wildcard",
+			inSourceExt:  json.RawMessage(``),
+			inExt:        json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"},"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["*"],"schain":{"complete":1,"nodes":[{"asi":"wildcard.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}} ]}}`),
+			outSourceExt: json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`),
+			outExt:       json.RawMessage(`{"prebid":{"aliases":{"appnexus":"alias1"}}}`),
+			hasError:     false,
+		},
+		{
+			description:  "Use source schain -- multiple (two) bidder schains in ext.prebid.schains",
+			inSourceExt:  json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"example.com","sid":"example1","rid":"ExampleReq1","hp":1}],"ver":"1.0"}}`),
+			inExt:        json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
+			outSourceExt: nil,
+			outExt:       nil,
+			hasError:     true,
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequest(t)
+		req.Source.Ext = test.inSourceExt
+		req.Ext = test.inExt
+
+		results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, config.Privacy{})
+		result := results["appnexus"]
+
+		if test.hasError == true {
+			assert.NotNil(t, errs)
+			assert.Nil(t, result)
+		} else {
+			assert.Nil(t, errs)
+			assert.Equal(t, test.outSourceExt, result.Source.Ext, test.description+":Source.Ext")
+			assert.Equal(t, test.outExt, result.Ext, test.description+":Ext")
+		}
 	}
 }
 
@@ -141,34 +292,47 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 		disabled int8 = 0
 	)
 	testCases := []struct {
-		description     string
-		lmt             *int8
-		enforceLMT      bool
-		expectDataScrub bool
+		description         string
+		lmt                 *int8
+		enforceLMT          bool
+		expectDataScrub     bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
 	}{
 		{
 			description:     "Feature Flag Enabled - OpenTRB Enabled",
 			lmt:             &enabled,
 			enforceLMT:      true,
 			expectDataScrub: true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: true,
+			},
 		},
 		{
 			description:     "Feature Flag Disabled - OpenTRB Enabled",
 			lmt:             &enabled,
 			enforceLMT:      false,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: false,
+			},
 		},
 		{
 			description:     "Feature Flag Enabled - OpenTRB Disabled",
 			lmt:             &disabled,
 			enforceLMT:      true,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: false,
+			},
 		},
 		{
 			description:     "Feature Flag Disabled - OpenTRB Disabled",
 			lmt:             &disabled,
 			enforceLMT:      false,
 			expectDataScrub: false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				LMTEnforced: false,
+			},
 		},
 	}
 
@@ -182,11 +346,10 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 			},
 		}
 
-		results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
 		result := results["appnexus"]
 
 		assert.Nil(t, errs)
-
 		if test.expectDataScrub {
 			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
@@ -194,6 +357,88 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
 		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
+	}
+}
+
+func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
+	testCases := []struct {
+		description         string
+		gdpr                string
+		gdprConsent         string
+		gdprScrub           bool
+		enforceGDPR         bool
+		expectPrivacyLabels pbsmetrics.PrivacyLabels
+	}{
+		{
+			description: "Enforce - TCF Invalid",
+			gdpr:        "1",
+			gdprConsent: "malformed",
+			gdprScrub:   false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: "",
+			},
+		},
+		{
+			description: "Enforce - TCF 1",
+			gdpr:        "1",
+			gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprScrub:   true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: pbsmetrics.TCFVersionV1,
+			},
+		},
+		{
+			description: "Enforce - TCF 2",
+			gdpr:        "1",
+			gdprConsent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+			gdprScrub:   true,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   true,
+				GDPRTCFVersion: pbsmetrics.TCFVersionV2,
+			},
+		},
+		{
+			description: "Not Enforce - TCF 1",
+			gdpr:        "0",
+			gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
+			gdprScrub:   false,
+			expectPrivacyLabels: pbsmetrics.PrivacyLabels{
+				GDPREnforced:   false,
+				GDPRTCFVersion: "",
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequest(t)
+		req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`)
+		req.Regs = &openrtb.Regs{
+			Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`),
+		}
+
+		privacyConfig := config.Privacy{
+			GDPR: config.GDPR{
+				TCF2: config.TCF2{
+					Enabled: true,
+				},
+			},
+		}
+
+		results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig)
+		result := results["appnexus"]
+
+		assert.Nil(t, errs)
+		if test.gdprScrub {
+			assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+		} else {
+			assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
+			assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+		}
+		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
 	}
 }
 
@@ -266,6 +511,7 @@ func newBidRequest(t *testing.T) *openrtb.BidRequest {
 		User: &openrtb.User{
 			ID:       "our-id",
 			BuyerUID: "their-id",
+			Yob:      1982,
 			Ext:      json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
 		},
 		Imp: []openrtb.Imp{{
@@ -302,5 +548,99 @@ func TestRandomizeList(t *testing.T) {
 	if len(adapters) != 1 {
 		t.Errorf("RandomizeList, expected a list of 1, found %d", len(adapters))
 	}
+}
+
+func TestBidderToPrebidChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
+				{
+					Bidders: []string{"Bidder1", "Bidder2"},
+					SChain: openrtb_ext.ExtRequestPrebidSChainSChain{
+						Complete: 1,
+						Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+							{
+								ASI:    "asi1",
+								SID:    "sid1",
+								Name:   "name1",
+								RID:    "rid1",
+								Domain: "domain1",
+								HP:     1,
+							},
+							{
+								ASI:    "asi2",
+								SID:    "sid2",
+								Name:   "name2",
+								RID:    "rid2",
+								Domain: "domain2",
+								HP:     2,
+							},
+						},
+						Ver: "version1",
+					},
+				},
+				{
+					Bidders: []string{"Bidder3", "Bidder4"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+			},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 4)
+	assert.Same(t, output["Bidder1"], &input.Prebid.SChains[0].SChain)
+	assert.Same(t, output["Bidder2"], &input.Prebid.SChains[0].SChain)
+	assert.Same(t, output["Bidder3"], &input.Prebid.SChains[1].SChain)
+	assert.Same(t, output["Bidder4"], &input.Prebid.SChains[1].SChain)
+}
+
+func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{
+				{
+					Bidders: []string{"Bidder1"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+				{
+					Bidders: []string{"Bidder1", "Bidder2"},
+					SChain:  openrtb_ext.ExtRequestPrebidSChainSChain{},
+				},
+			},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
+
+	assert.NotNil(t, err)
+	assert.Nil(t, output)
+}
+
+func TestBidderToPrebidChainsNilSChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: nil,
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
+
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 0)
+}
+
+func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) {
+	input := openrtb_ext.ExtRequest{
+		Prebid: openrtb_ext.ExtRequestPrebid{
+			SChains: []*openrtb_ext.ExtRequestPrebidSChain{},
+		},
+	}
+
+	output, err := BidderToPrebidSChains(&input)
 
+	assert.Nil(t, err)
+	assert.Equal(t, len(output), 0)
 }
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index f05f25e8..05b2fb6d 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -276,7 +276,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
 		},
 	}
 
-	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8
 	// PI needs all purposes to succeed
 	testDefs := []tcf2TestDef{
 		{
diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go
index 6d9eaf9f..2faa0849 100644
--- a/pbsmetrics/go_metrics_test.go
+++ b/pbsmetrics/go_metrics_test.go
@@ -56,10 +56,14 @@ func TestNewMetrics(t *testing.T) {
 
 	ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess)
 	ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure)
-	ensureContains(t, registry, "privacy.request.tcf.v1", m.TCFReqVersion[TCFVersionV1])
-	ensureContains(t, registry, "privacy.request.tcf.v2", m.TCFReqVersion[TCFVersionV2])
-	ensureContains(t, registry, "privacy.request.tcf.err", m.TCFReqVersion[TCFVersionErr])
 
+	ensureContains(t, registry, "privacy.request.ccpa.specified", m.PrivacyCCPARequest)
+	ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut)
+	ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest)
+	ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest)
+	ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1])
+	ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2])
+	ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr])
 }
 
 func TestRecordBidType(t *testing.T) {
@@ -202,6 +206,61 @@ func TestRecordPrebidCacheRequestTimeWithNotSuccess(t *testing.T) {
 	assert.Equal(t, m.PrebidCacheRequestTimerError.Count(), int64(1))
 }
 
+func TestRecordRequestPrivacy(t *testing.T) {
+	registry := metrics.NewRegistry()
+	m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, config.DisabledMetrics{AccountAdapterDetails: true})
+
+	// CCPA
+	m.RecordRequestPrivacy(PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: true,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: false,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		CCPAEnforced: false,
+		CCPAProvided: true,
+	})
+
+	// COPPA
+	m.RecordRequestPrivacy(PrivacyLabels{
+		COPPAEnforced: true,
+	})
+
+	// LMT
+	m.RecordRequestPrivacy(PrivacyLabels{
+		LMTEnforced: true,
+	})
+
+	// GDPR
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionErr,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionV1,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionV2,
+	})
+	m.RecordRequestPrivacy(PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: TCFVersionV1,
+	})
+
+	assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA")
+	assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out")
+	assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA")
+	assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT")
+	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err")
+	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1")
+	assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2")
+}
+
 func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) {
 	ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter)
 	ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter)
diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go
index 03daff0d..b722ab28 100644
--- a/pbsmetrics/prometheus/prometheus_test.go
+++ b/pbsmetrics/prometheus/prometheus_test.go
@@ -944,33 +944,96 @@ func TestTimeoutNotifications(t *testing.T) {
 
 }
 
-func TestTCFMetrics(t *testing.T) {
+func TestRecordRequestPrivacy(t *testing.T) {
 	m := createMetricsForTesting()
 
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(0))
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1))
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(2))
-	m.RecordTCFReq(pbsmetrics.TCFVersionToValue(1))
+	// CCPA
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: true,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		CCPAEnforced: true,
+		CCPAProvided: false,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		CCPAEnforced: false,
+		CCPAProvided: true,
+	})
+
+	// COPPA
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		COPPAEnforced: true,
+	})
 
-	assertCounterVecValue(t, "", "privacy_tcf:err", m.tcfVersion,
+	// LMT
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		LMTEnforced: true,
+	})
+
+	// GDPR
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionErr,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionV1,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionV2,
+	})
+	m.RecordRequestPrivacy(pbsmetrics.PrivacyLabels{
+		GDPREnforced:   true,
+		GDPRTCFVersion: pbsmetrics.TCFVersionV1,
+	})
+
+	assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+			optOutLabel: "true",
+		})
+
+	assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+			optOutLabel: "false",
+		})
+
+	assertCounterVecValue(t, "", "privacy_coppa", m.privacyCOPPA,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+		})
+
+	assertCounterVecValue(t, "", "privacy_lmt", m.privacyLMT,
+		float64(1),
+		prometheus.Labels{
+			sourceLabel: sourceRequest,
+		})
+
+	assertCounterVecValue(t, "", "privacy_tcf:err", m.privacyTCF,
 		float64(1),
 		prometheus.Labels{
-			versionLabel: "err",
 			sourceLabel:  sourceRequest,
+			versionLabel: "err",
 		})
 
-	assertCounterVecValue(t, "", "privacy_tcf:v1", m.tcfVersion,
+	assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF,
 		float64(2),
 		prometheus.Labels{
-			versionLabel: "v1",
 			sourceLabel:  sourceRequest,
+			versionLabel: "v1",
 		})
 
-	assertCounterVecValue(t, "", "privacy_tcf:v2", m.tcfVersion,
+	assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF,
 		float64(1),
 		prometheus.Labels{
-			versionLabel: "v2",
 			sourceLabel:  sourceRequest,
+			versionLabel: "v2",
 		})
 }