Repository: prebid/prebid-server
Pull request #1322 (2020-05-29 17:52:58)

Timeout notification monitoring and debugging


Patch:
diff --git a/.travis.yml b/.travis.yml
index 3cc55e5f..ea2c46c4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@ language: go
 go:
   - '1.12'
   - '1.13'
+  - '1.14'
 
 go_import_path: github.com/prebid/prebid-server
 
diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go
index 7f0262fd..78a71dcf 100644
--- a/adapters/adtelligent/adtelligent.go
+++ b/adapters/adtelligent/adtelligent.go
@@ -55,7 +55,6 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *
 
 	imps := request.Imp
 	request.Imp = make([]openrtb.Imp, 0, len(imps))
-
 	for sourceId, impIds := range imp2source {
 		request.Imp = request.Imp[:0]
 
diff --git a/adapters/beachfront/usersync.go b/adapters/beachfront/usersync.go
index a255d4d9..a6502331 100644
--- a/adapters/beachfront/usersync.go
+++ b/adapters/beachfront/usersync.go
@@ -7,6 +7,12 @@ import (
 	"github.com/prebid/prebid-server/usersync"
 )
 
+var VENDOR_ID uint16 = 335
+
 func NewBeachfrontSyncer(temp *template.Template) usersync.Usersyncer {
-	return adapters.NewSyncer("beachfront", 0, temp, adapters.SyncTypeIframe)
+	return adapters.NewSyncer(
+		"beachfront",
+		VENDOR_ID,
+		temp,
+		adapters.SyncTypeIframe)
 }
diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go
new file mode 100644
index 00000000..6b4f698d
--- /dev/null
+++ b/adapters/dmx/dmx.go
@@ -0,0 +1,296 @@
+package dmx
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+type DmxAdapter struct {
+	endpoint string
+}
+
+func NewDmxBidder(endpoint string) *DmxAdapter {
+	return &DmxAdapter{endpoint: endpoint}
+}
+
+type dmxExt struct {
+	Bidder dmxParams `json:"bidder"`
+}
+
+type dmxParams struct {
+	TagId       string `json:"tagid,omitempty"`
+	DmxId       string `json:"dmxid,omitempty"`
+	MemberId    string `json:"memberid,omitempty"`
+	PublisherId string `json:"publisher_id,omitempty"`
+	SellerId    string `json:"seller_id,omitempty"`
+}
+
+func UserSellerOrPubId(str1, str2 string) string {
+	if str1 != "" {
+		return str1
+	}
+	return str2
+}
+
+func (adapter *DmxAdapter) MakeRequests(request *openrtb.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) {
+	var imps []openrtb.Imp
+	var rootExtInfo dmxExt
+	var publisherId string
+	var sellerId string
+	var userExt openrtb_ext.ExtUser
+	var anyHasId = false
+	var reqCopy openrtb.BidRequest = *request
+	var dmxReq *openrtb.BidRequest = &reqCopy
+
+	if request.User == nil {
+		if request.App == nil {
+			return nil, []error{errors.New("No user id or app id found. Could not send request to DMX.")}
+		}
+	}
+
+	if len(request.Imp) >= 1 {
+		err := json.Unmarshal(request.Imp[0].Ext, &rootExtInfo)
+		if err != nil {
+			errs = append(errs, err)
+		} else {
+			publisherId = UserSellerOrPubId(rootExtInfo.Bidder.PublisherId, rootExtInfo.Bidder.MemberId)
+			sellerId = rootExtInfo.Bidder.SellerId
+		}
+	}
+
+	if request.App != nil {
+		appCopy := *request.App
+		appPublisherCopy := *request.App.Publisher
+		dmxReq.App = &appCopy
+		dmxReq.App.Publisher = &appPublisherCopy
+		if dmxReq.App.ID != "" {
+			anyHasId = true
+		}
+	} else {
+		dmxReq.App = nil
+	}
+
+	if request.Site != nil {
+		siteCopy := *request.Site
+		sitePublisherCopy := *request.Site.Publisher
+		dmxReq.Site = &siteCopy
+		dmxReq.Site.Publisher = &sitePublisherCopy
+		if dmxReq.Site.Publisher != nil {
+			dmxReq.Site.Publisher.ID = publisherId
+		} else {
+			dmxReq.Site.Publisher = &openrtb.Publisher{ID: publisherId}
+		}
+	} else {
+		dmxReq.Site = nil
+	}
+
+	if request.User != nil {
+		userCopy := *request.User
+		dmxReq.User = &userCopy
+	} else {
+		dmxReq.User = nil
+	}
+
+	if dmxReq.User != nil {
+		if dmxReq.User.ID != "" {
+			anyHasId = true
+		}
+		if dmxReq.User.Ext != nil {
+			if err := json.Unmarshal(dmxReq.User.Ext, &userExt); err == nil {
+				if len(userExt.Eids) > 0 || (userExt.DigiTrust != nil && userExt.DigiTrust.ID != "") {
+					anyHasId = true
+				}
+			}
+		}
+	}
+
+	if anyHasId == false {
+		return nil, []error{errors.New("This request contained no identifier")}
+	}
+
+	for _, inst := range dmxReq.Imp {
+		var banner *openrtb.Banner
+		var video *openrtb.Video
+		var ins openrtb.Imp
+		var params dmxExt
+		const intVal int8 = 1
+		source := (*json.RawMessage)(&inst.Ext)
+		if err := json.Unmarshal(*source, &params); err != nil {
+			errs = append(errs, err)
+		}
+		if isDmxParams(params.Bidder) {
+			if inst.Banner != nil {
+				if len(inst.Banner.Format) != 0 {
+					banner = inst.Banner
+					if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" {
+						imps = fetchParams(params, inst, ins, imps, banner, nil, intVal)
+					} else {
+						return nil, []error{errors.New("Missing Params for auction to be send")}
+					}
+				}
+			}
+
+			if inst.Video != nil {
+				video = inst.Video
+				if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" {
+					imps = fetchParams(params, inst, ins, imps, nil, video, intVal)
+				} else {
+					return nil, []error{errors.New("Missing Params for auction to be send")}
+				}
+			}
+		}
+
+	}
+
+	dmxReq.Imp = imps
+
+	oJson, err := json.Marshal(dmxReq)
+
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "Application/json;charset=utf-8")
+	reqBidder := &adapters.RequestData{
+		Method:  "POST",
+		Uri:     adapter.endpoint + addParams(sellerId), //adapter.endpoint,
+		Body:    oJson,
+		Headers: headers,
+	}
+	reqsBidder = append(reqsBidder, reqBidder)
+	return
+}
+
+func (adapter *DmxAdapter) MakeBids(request *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	if http.StatusNoContent == response.StatusCode {
+		return nil, nil
+	}
+
+	if http.StatusBadRequest == response.StatusCode {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected status code 400"),
+		}}
+	}
+
+	if http.StatusOK != response.StatusCode {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected response no status code"),
+		}}
+	}
+
+	var bidResp openrtb.BidResponse
+
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+
+	for _, sb := range bidResp.SeatBid {
+		for i := range sb.Bid {
+			bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, request.Imp)
+			if err != nil {
+				errs = append(errs, err)
+			} else {
+				b := &adapters.TypedBid{
+					Bid:     &sb.Bid[i],
+					BidType: bidType,
+				}
+				if b.BidType == openrtb_ext.BidTypeVideo {
+					b.Bid.AdM = videoImpInsertion(b.Bid)
+				}
+				bidResponse.Bids = append(bidResponse.Bids, b)
+			}
+		}
+	}
+	return bidResponse, errs
+
+}
+
+func fetchParams(params dmxExt, inst openrtb.Imp, ins openrtb.Imp, imps []openrtb.Imp, banner *openrtb.Banner, video *openrtb.Video, intVal int8) []openrtb.Imp {
+	if params.Bidder.TagId != "" {
+		ins = openrtb.Imp{
+			ID:     inst.ID,
+			TagID:  params.Bidder.TagId,
+			Ext:    inst.Ext,
+			Secure: &intVal,
+		}
+	}
+
+	if params.Bidder.DmxId != "" {
+		ins = openrtb.Imp{
+			ID:     inst.ID,
+			TagID:  params.Bidder.DmxId,
+			Ext:    inst.Ext,
+			Secure: &intVal,
+		}
+	}
+	if banner != nil {
+		ins.Banner = banner
+	}
+
+	if video != nil {
+		ins.Video = video
+	}
+
+	if ins.TagID == "" {
+		return imps
+	}
+	imps = append(imps, ins)
+	return imps
+}
+
+func addParams(str string) string {
+	if str != "" {
+		return "?sellerid=" + url.QueryEscape(str)
+	}
+	return ""
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impID {
+			if imp.Banner == nil && imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			}
+			return mediaType, nil
+		}
+	}
+
+	// This shouldnt happen. Lets handle it just incase by returning an error.
+	return "", &errortypes.BadInput{
+		Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID),
+	}
+}
+
+func videoImpInsertion(bid *openrtb.Bid) string {
+	adm := bid.AdM
+	nurl := bid.NURL
+	search := "</Impression>"
+	imp := "</Impression><Impression><![CDATA[%s]]></Impression>"
+	wrapped_nurl := fmt.Sprintf(imp, nurl)
+	results := strings.Replace(adm, search, wrapped_nurl, 1)
+	return results
+}
+
+func isDmxParams(t interface{}) bool {
+	switch t.(type) {
+	case dmxParams:
+		return true
+	default:
+		return false
+	}
+}
diff --git a/adapters/dmx/usersync.go b/adapters/dmx/usersync.go
new file mode 100644
index 00000000..98e56234
--- /dev/null
+++ b/adapters/dmx/usersync.go
@@ -0,0 +1,12 @@
+package dmx
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewDmxSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("dmx", 144, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go
index 5fb9ccf2..2a46b546 100644
--- a/adapters/eplanning/eplanning.go
+++ b/adapters/eplanning/eplanning.go
@@ -133,7 +133,6 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqIn
 
 	uriObj.Path = uriObj.Path + fmt.Sprintf("/%s/%s/%s/%s", clientID, dfpClientID, requestTarget, sec)
 	query := url.Values{}
-	query.Set("r", "pbs")
 	query.Set("ncb", "1")
 	if request.App == nil {
 		query.Set("ur", pageURL)
diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go
index 9edafa86..294d0d10 100644
--- a/adapters/telaria/telaria.go
+++ b/adapters/telaria/telaria.go
@@ -1,8 +1,6 @@
 package telaria
 
 import (
-	"bytes"
-	"compress/gzip"
 	"encoding/json"
 	"fmt"
 	"github.com/mxmCherry/openrtb"
@@ -79,7 +77,6 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header {
 	headers.Add("Content-Type", "application/json;charset=utf-8")
 	headers.Add("Accept", "application/json")
 	headers.Add("X-Openrtb-Version", "2.5")
-	headers.Add("Accept-Encoding", "gzip")
 
 	if request.Device != nil {
 		if len(request.Device.UA) > 0 {
@@ -233,34 +230,6 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *ad
 	}}, nil
 }
 
-// response isn't automatically decompressed. This method unzips the response if Content-Encoding is gzip
-func GetResponseBody(response *adapters.ResponseData) ([]byte, error) {
-
-	if "gzip" == response.Headers.Get("Content-Encoding") {
-		body := bytes.NewBuffer(response.Body)
-		r, readerErr := gzip.NewReader(body)
-		if readerErr != nil {
-			return nil, &errortypes.BadServerResponse{
-				Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode),
-			}
-		}
-		var resB bytes.Buffer
-		var err error
-		_, err = resB.ReadFrom(r)
-		if err != nil {
-			return nil, &errortypes.BadServerResponse{
-				Message: fmt.Sprintf("Error while trying to unzip data [ %d ]", response.StatusCode),
-			}
-		}
-
-		response.Headers.Del("Content-Encoding")
-
-		return resB.Bytes(), nil
-	} else {
-		return response.Body, nil
-	}
-}
-
 func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error {
 	if response.StatusCode == http.StatusNoContent {
 		return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"}
@@ -294,11 +263,7 @@ func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR
 		return nil, []error{httpStatusError}
 	}
 
-	responseBody, err := GetResponseBody(response)
-
-	if err != nil {
-		return nil, []error{err}
-	}
+	responseBody := response.Body
 
 	var bidResp openrtb.BidResponse
 	if err := json.Unmarshal(responseBody, &bidResp); err != nil {
diff --git a/adapters/yieldlab/const.go b/adapters/yieldlab/const.go
new file mode 100644
index 00000000..cb66cb6d
--- /dev/null
+++ b/adapters/yieldlab/const.go
@@ -0,0 +1,7 @@
+package yieldlab
+
+const adSlotIdSeparator = ","
+const adsizeSeparator = "x"
+const adSourceBanner = "<script src=\"%v\"></script>"
+const adSourceURL = "https://ad.yieldlab.net/d/%v/%v/%v?%v"
+const creativeID = "%v%v%v"
diff --git a/adapters/yieldlab/types.go b/adapters/yieldlab/types.go
new file mode 100644
index 00000000..90612700
--- /dev/null
+++ b/adapters/yieldlab/types.go
@@ -0,0 +1,29 @@
+package yieldlab
+
+import (
+	"strconv"
+	"time"
+)
+
+type bidResponse struct {
+	ID         uint64 `json:"id"`
+	Price      uint   `json:"price"`
+	Advertiser string `json:"advertiser"`
+	Adsize     string `json:"adsize"`
+	Pid        uint64 `json:"pid"`
+	Did        uint64 `json:"did"`
+	Pvid       string `json:"pvid"`
+}
+
+type cacheBuster func() string
+
+type weekGenerator func() string
+
+var defaultCacheBuster cacheBuster = func() string {
+	return strconv.FormatInt(time.Now().Unix(), 10)
+}
+
+var defaultWeekGenerator weekGenerator = func() string {
+	_, week := time.Now().ISOWeek()
+	return strconv.Itoa(week)
+}
diff --git a/adapters/yieldlab/usersync.go b/adapters/yieldlab/usersync.go
new file mode 100644
index 00000000..3ee9a3fd
--- /dev/null
+++ b/adapters/yieldlab/usersync.go
@@ -0,0 +1,12 @@
+package yieldlab
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewYieldlabSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("yieldlab", 70, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go
new file mode 100644
index 00000000..20f36747
--- /dev/null
+++ b/adapters/yieldlab/yieldlab.go
@@ -0,0 +1,314 @@
+package yieldlab
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"path"
+	"strconv"
+	"strings"
+
+	"github.com/mxmCherry/openrtb"
+	"golang.org/x/text/currency"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// YieldlabAdapter connects the Yieldlab API to prebid server
+type YieldlabAdapter struct {
+	endpoint    string
+	cacheBuster cacheBuster
+	getWeek     weekGenerator
+}
+
+// NewYieldlabBidder returns a new YieldlabBidder instance
+func NewYieldlabBidder(endpoint string) *YieldlabAdapter {
+	return &YieldlabAdapter{
+		endpoint:    endpoint,
+		cacheBuster: defaultCacheBuster,
+		getWeek:     defaultWeekGenerator,
+	}
+}
+
+// Builds endpoint url based on adapter-specific pub settings from imp.ext
+func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) {
+	uri, err := url.Parse(a.endpoint)
+	if err != nil {
+		return "", fmt.Errorf("failed to parse yieldlab endpoint: %v", err)
+	}
+
+	uri.Path = path.Join(uri.Path, params.AdslotID)
+	q := uri.Query()
+	q.Set("content", "json")
+	q.Set("pvid", "true")
+	q.Set("ts", a.cacheBuster())
+	q.Set("t", a.makeTargetingValues(params))
+
+	if req.User != nil && req.User.BuyerUID != "" {
+		q.Set("ids", "ylid:"+req.User.BuyerUID)
+	}
+
+	if req.Device != nil {
+		q.Set("yl_rtb_ifa", req.Device.IFA)
+		q.Set("yl_rtb_devicetype", fmt.Sprintf("%v", req.Device.DeviceType))
+
+		if req.Device.ConnectionType != nil {
+			q.Set("yl_rtb_connectiontype", fmt.Sprintf("%v", req.Device.ConnectionType.Val()))
+		}
+
+		if req.Device.Geo != nil {
+			q.Set("lat", fmt.Sprintf("%v", req.Device.Geo.Lat))
+			q.Set("lon", fmt.Sprintf("%v", req.Device.Geo.Lon))
+		}
+	}
+
+	if req.App != nil {
+		q.Set("pubappname", req.App.Name)
+		q.Set("pubbundlename", req.App.Bundle)
+	}
+
+	gdpr, consent, err := a.getGDPR(req)
+	if err != nil {
+		return "", err
+	}
+	if gdpr != "" && consent != "" {
+		q.Set("gdpr", gdpr)
+		q.Set("consent", consent)
+	}
+
+	uri.RawQuery = q.Encode()
+
+	return uri.String(), nil
+}
+
+func (a *YieldlabAdapter) getGDPR(request *openrtb.BidRequest) (string, string, error) {
+	gdpr := ""
+	var extRegs openrtb_ext.ExtRegs
+	if request.Regs != nil {
+		if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil {
+			return "", "", fmt.Errorf("failed to parse ExtRegs in Yieldlab GDPR check: %v", err)
+		}
+		if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) {
+			gdpr = strconv.Itoa(int(*extRegs.GDPR))
+		}
+	}
+
+	consent := ""
+	if request.User != nil && request.User.Ext != nil {
+		var extUser openrtb_ext.ExtUser
+		if err := json.Unmarshal(request.User.Ext, &extUser); err != nil {
+			return "", "", fmt.Errorf("failed to parse ExtUser in Yieldlab GDPR check: %v", err)
+		}
+		consent = extUser.Consent
+	}
+
+	return gdpr, consent, nil
+}
+
+func (a *YieldlabAdapter) makeTargetingValues(params *openrtb_ext.ExtImpYieldlab) string {
+	values := url.Values{}
+	for k, v := range params.Targeting {
+		values.Set(k, v)
+	}
+	return values.Encode()
+}
+
+func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	if len(request.Imp) == 0 {
+		return nil, []error{fmt.Errorf("invalid request %+v, no Impressions given", request)}
+	}
+
+	bidURL, err := a.makeEndpointURL(request, a.mergeParams(a.parseRequest(request)))
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Accept", "application/json")
+	if request.Site != nil {
+		headers.Add("Referer", request.Site.Page)
+	}
+	if request.Device != nil {
+		headers.Add("User-Agent", request.Device.UA)
+		headers.Add("X-Forwarded-For", request.Device.IP)
+	}
+	if request.User != nil {
+		headers.Add("Cookie", "id="+request.User.BuyerUID)
+	}
+
+	return []*adapters.RequestData{{
+		Method:  "GET",
+		Uri:     bidURL,
+		Headers: headers,
+	}}, nil
+}
+
+// parseRequest extracts the Yieldlab request information from the request
+func (a *YieldlabAdapter) parseRequest(request *openrtb.BidRequest) []*openrtb_ext.ExtImpYieldlab {
+	params := make([]*openrtb_ext.ExtImpYieldlab, 0)
+
+	for i := 0; i < len(request.Imp); i++ {
+		bidderExt := new(adapters.ExtImpBidder)
+		if err := json.Unmarshal(request.Imp[i].Ext, bidderExt); err != nil {
+			continue
+		}
+
+		yieldlabExt := new(openrtb_ext.ExtImpYieldlab)
+		if err := json.Unmarshal(bidderExt.Bidder, yieldlabExt); err != nil {
+			continue
+		}
+
+		params = append(params, yieldlabExt)
+	}
+
+	return params
+}
+
+func (a *YieldlabAdapter) mergeParams(params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab {
+	var adSlotIds []string
+	targeting := make(map[string]string)
+
+	for _, p := range params {
+		adSlotIds = append(adSlotIds, p.AdslotID)
+		for k, v := range p.Targeting {
+			targeting[k] = v
+		}
+	}
+
+	return &openrtb_ext.ExtImpYieldlab{
+		AdslotID:  strings.Join(adSlotIds, adSlotIdSeparator),
+		Targeting: targeting,
+	}
+}
+
+// MakeBids make the bids for the bid response.
+func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode != 200 {
+		return nil, []error{
+			&errortypes.BadServerResponse{
+				Message: fmt.Sprintf("failed to resolve bids from yieldlab response: Unexpected response code %v", response.StatusCode),
+			},
+		}
+	}
+
+	bids := make([]*bidResponse, 0)
+	if err := json.Unmarshal(response.Body, &bids); err != nil {
+		return nil, []error{
+			&errortypes.BadServerResponse{
+				Message: fmt.Sprintf("failed to parse bids response from yieldlab: %v", err),
+			},
+		}
+	}
+
+	params := a.parseRequest(internalRequest)
+
+	bidderResponse := &adapters.BidderResponse{
+		Currency: currency.EUR.String(),
+		Bids:     []*adapters.TypedBid{},
+	}
+
+	for i, bid := range bids {
+		width, height, err := splitSize(bid.Adsize)
+		if err != nil {
+			return nil, []error{err}
+		}
+
+		req := a.findBidReq(bid.ID, params)
+		if req == nil {
+			return nil, []error{
+				fmt.Errorf("failed to find yieldlab request for adslotID %v. This is most likely a programming issue", bid.ID),
+			}
+		}
+
+		var bidType openrtb_ext.BidType
+		responseBid := &openrtb.Bid{
+			ID:     strconv.FormatUint(bid.ID, 10),
+			Price:  float64(bid.Price) / 100,
+			ImpID:  internalRequest.Imp[i].ID,
+			CrID:   a.makeCreativeID(req, bid),
+			DealID: strconv.FormatUint(bid.Pid, 10),
+			W:      width,
+			H:      height,
+		}
+
+		if internalRequest.Imp[i].Video != nil {
+			bidType = openrtb_ext.BidTypeVideo
+			responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid)
+
+		} else if internalRequest.Imp[i].Banner != nil {
+			bidType = openrtb_ext.BidTypeBanner
+			responseBid.AdM = a.makeBannerAdSource(internalRequest, req, bid)
+		} else {
+			// Yieldlab adapter currently doesn't support Audio and Native ads
+			continue
+		}
+
+		bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+			BidType: bidType,
+			Bid:     responseBid,
+		})
+	}
+
+	return bidderResponse, nil
+}
+
+func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab {
+	slotIdStr := strconv.FormatUint(adslotID, 10)
+	for _, p := range params {
+		if p.AdslotID == slotIdStr {
+			return p
+		}
+	}
+
+	return nil
+}
+
+func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string {
+	return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res))
+}
+
+func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string {
+	val := url.Values{}
+	val.Set("ts", a.cacheBuster())
+	val.Set("id", ext.ExtId)
+	val.Set("pvid", res.Pvid)
+
+	if req.User != nil {
+		val.Set("ids", "ylid:"+req.User.BuyerUID)
+	}
+
+	gdpr, consent, err := a.getGDPR(req)
+	if err == nil && gdpr != "" && consent != "" {
+		val.Set("gdpr", gdpr)
+		val.Set("consent", consent)
+	}
+
+	return fmt.Sprintf(adSourceURL, ext.AdslotID, ext.SupplyID, res.Adsize, val.Encode())
+}
+
+func (a *YieldlabAdapter) makeCreativeID(req *openrtb_ext.ExtImpYieldlab, bid *bidResponse) string {
+	return fmt.Sprintf(creativeID, req.AdslotID, bid.Pid, a.getWeek())
+}
+
+func splitSize(size string) (uint64, uint64, error) {
+	sizeParts := strings.Split(size, adsizeSeparator)
+	if len(sizeParts) != 2 {
+		return 0, 0, nil
+	}
+
+	width, err := strconv.ParseUint(sizeParts[0], 10, 64)
+	if err != nil {
+		return 0, 0, fmt.Errorf("failed to parse yieldlab adsize: %v", err)
+	}
+
+	height, err := strconv.ParseUint(sizeParts[1], 10, 64)
+	if err != nil {
+		return 0, 0, fmt.Errorf("failed to parse yieldlab adsize: %v", err)
+	}
+
+	return width, height, nil
+
+}
diff --git a/config/config.go b/config/config.go
index 5c66f4cd..559fc5da 100755
--- a/config/config.go
+++ b/config/config.go
@@ -66,6 +66,8 @@ type Configuration struct {
 	PemCertsFile string `mapstructure:"certificates_file"`
 	// Custom headers to handle request timeouts from queueing infrastructure
 	RequestTimeoutHeaders RequestTimeoutHeaders `mapstructure:"request_timeout_headers"`
+	// Debug/logging flags go here
+	Debug Debug `mapstructure:"debug"`
 }
 
 const MIN_COOKIE_SIZE_BYTES = 500
@@ -104,6 +106,7 @@ func (cfg *Configuration) validate() configErrors {
 	errs = cfg.GDPR.validate(errs)
 	errs = cfg.CurrencyConverter.validate(errs)
 	errs = validateAdapters(cfg.Adapters, errs)
+	errs = cfg.Debug.validate(errs)
 	return errs
 }
 
@@ -142,6 +145,7 @@ type GDPR struct {
 	Timeouts                GDPRTimeouts `mapstructure:"timeouts_ms"`
 	NonStandardPublishers   []string     `mapstructure:"non_standard_publishers,flow"`
 	NonStandardPublisherMap map[string]int
+	TCF2                    TCF2 `mapstructure:"tcf2"`
 	AMPException            bool `mapstructure:"amp_exception"`
 }
 
@@ -165,6 +169,26 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration {
 	return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond
 }
 
+// TCF2 defines the TCF2 specific configurations for GDPR
+type TCF2 struct {
+	Enabled             bool                 `mapstructure:"enabled"`
+	Purpose1            PurposeDetail        `mapstructure:"purpose1"`
+	Purpose2            PurposeDetail        `mapstructure:"purpose2"`
+	Purpose7            PurposeDetail        `mapstructure:"purpose7"`
+	SpecialPurpose1     PurposeDetail        `mapstructure:"special_purpose1"`
+	PurposeOneTreatment PurposeOneTreatement `mapstructure:"purpose_one_treatement"`
+}
+
+// Making a purpose struct so purpose specific details can be added later.
+type PurposeDetail struct {
+	Enabled bool `mapstructure:"enabled"`
+}
+
+type PurposeOneTreatement struct {
+	Enabled       bool `mapstructure:"enabled"`
+	AccessAllowed bool `mapstructure:"access_allowed"`
+}
+
 type CCPA struct {
 	Enforce bool `mapstructure:"enforce"`
 }
@@ -429,6 +453,30 @@ type DefReqFiles struct {
 	FileName string `mapstructure:"name"`
 }
 
+type Debug struct {
+	TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"`
+}
+
+func (cfg *Debug) validate(errs configErrors) configErrors {
+	return cfg.TimeoutNotification.validate(errs)
+}
+
+type TimeoutNotification struct {
+	// Log timeout notifications in the application log
+	Log bool `mapstructure:"log"`
+	// Fraction of notifications to log
+	SamplingRate float32 `mapstructure:"sampling_rate"`
+	// Only log failures
+	FailOnly bool `mapstructure:"fail_only"`
+}
+
+func (cfg *TimeoutNotification) validate(errs configErrors) configErrors {
+	if cfg.SamplingRate < 0.0 || cfg.SamplingRate > 1.0 {
+		errs = append(errs, fmt.Errorf("debug.timeout_notification.sampling_rate must be positive and not greater than 1.0. Got %f", cfg.SamplingRate))
+	}
+	return errs
+}
+
 // New uses viper to get our server configurations.
 func New(v *viper.Viper) (*Configuration, error) {
 	var c Configuration
@@ -500,7 +548,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
-	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24")
 	// openrtb_ext.BidderAdOcean doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -513,6 +561,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -551,8 +600,9 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D")
 	// openrtb_ext.BidderVrtcal doesn't have a good default.
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldlab, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldmo, "https://ads.yieldmo.com/pbsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
-	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_sc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderYieldone, "https://y.one.impact-ad.jp/hbs_cs?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderZeroClickFraud, "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")
 }
 
@@ -689,9 +739,10 @@ func SetupViper(v *viper.Viper, filename string) {
 	// for them and specify all the parameters they need for them to work correctly.
 	v.SetDefault("adapters.audiencenetwork.disabled", true)
 	v.SetDefault("adapters.rubicon.disabled", true)
-
 	v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb")
 	v.SetDefault("adapters.33across.partner_id", "")
+	v.SetDefault("adapters.dmx.endpoint", "https://dmx.districtm.io/b/v2")
+	v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction")
 	v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx")
 	v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
 	v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json")
@@ -701,7 +752,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}")
 	v.SetDefault("adapters.adoppler.endpoint", "http://app.trustedmarketplace.io/ads")
 	v.SetDefault("adapters.adpone.endpoint", "http://rtb.adpone.com/bid-request?src=prebid_server")
-	v.SetDefault("adapters.adtelligent.endpoint", "http://hb.adtelligent.com/auction")
+	v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb")
 	v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4")
 	v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid")
@@ -717,7 +768,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
 	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", "https://ads.us.e-planning.net/hb/1")
+	v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1")
 	v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/")
 	v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io")
 	v.SetDefault("adapters.grid.endpoint", "http://grid.bidswitch.net/sp_bid?sp=prebid")
@@ -760,6 +811,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard")
 	v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804")
 	v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid")
+	v.SetDefault("adapters.yieldlab.endpoint", "https://ad.yieldlab.net/yp/")
 	v.SetDefault("adapters.yieldmo.endpoint", "https://ads.yieldmo.com/exchange/prebid-server")
 	v.SetDefault("adapters.yieldone.endpoint", "https://y.one.impact-ad.jp/hbs_imp")
 	v.SetDefault("adapters.zeroclickfraud.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
@@ -772,6 +824,14 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0)
 	v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0)
 	v.SetDefault("gdpr.non_standard_publishers", []string{""})
+	v.SetDefault("gdpr.tcf2.enabled", true)
+	v.SetDefault("gdpr.tcf2.purpose1.enabled", true)
+	v.SetDefault("gdpr.tcf2.purpose2.enabled", true)
+	v.SetDefault("gdpr.tcf2.purpose4.enabled", true)
+	v.SetDefault("gdpr.tcf2.purpose7.enabled", true)
+	v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true)
+	v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true)
+	v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true)
 	v.SetDefault("gdpr.amp_exception", false)
 	v.SetDefault("ccpa.enforce", false)
 	v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json")
@@ -787,6 +847,10 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("request_timeout_headers.request_time_in_queue", "")
 	v.SetDefault("request_timeout_headers.request_timeout_in_queue", "")
 
+	v.SetDefault("debug.timeout_notification.log", false)
+	v.SetDefault("debug.timeout_notification.sampling_rate", 0.0)
+	v.SetDefault("debug.timeout_notification.fail_only", false)
+
 	// Set environment variable support:
 	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
 	v.SetEnvPrefix("PBS")
diff --git a/config/util/loggers.go b/config/util/loggers.go
new file mode 100644
index 00000000..88702e68
--- /dev/null
+++ b/config/util/loggers.go
@@ -0,0 +1,24 @@
+package util
+
+import (
+	"math/rand"
+)
+
+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) {
+	logRandomSampleImpl(msg, logger, chance, rand.Float32)
+}
+
+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
+	}
+	logger(msg)
+}
diff --git a/docs/bidders/beachfront.md b/docs/bidders/beachfront.md
index bb51db41..ecd7a8f9 100644
--- a/docs/bidders/beachfront.md
+++ b/docs/bidders/beachfront.md
@@ -1,7 +1,13 @@
 # Beachfront bidder
 
-To use the beachfront bidder you will need an appId from an exchange 
-account on [https://platform.beachfront.io](platform.beachfront.io).
+To use the beachfront bidder you will need an appId (Exchange Id) from an exchange 
+account on [platform.beachfront.io](https://platform.beachfront.io).
 
 For further information, please contact adops@beachfront.com.
 
+As seen in the JSON response from \{your PBS server\}\/bidder\/params [(example)](https://prebid.adnxs.com/pbs/v1/bidders/params), the beachfront bidder can take either an "appId" parameter, or an "appIds" parameter. If the request is for one media type, the appId parameter should be used with the value of the Exchange Id on the Beachfront platform.
+
+The appIds parameter is for requesting a mix of banner and video. It has two parameters, "banner", and "video" for the appIds of two appropriately configured exchanges on the platform. The appIds parameter can be sent with just one of its two parameters and it will behave like the appId parameter.
+
+If the request includes an appId configured for a video response, the videoResponseType parameter can be defined as "nurl", "adm" or "both". These will apply to all video returned. If it is not defined, the response type will be a nurl. The definitions for "nurl" vs. "adm" are here: (https://github.com/mxmCherry/openrtb/blob/master/openrtb2/bid.go).
+
diff --git a/docs/bidders/pubnative.md b/docs/bidders/pubnative.md
new file mode 100644
index 00000000..a25cafe0
--- /dev/null
+++ b/docs/bidders/pubnative.md
@@ -0,0 +1,62 @@
+# Pubnative Bidder
+
+## Prerequisite
+Before adding PubNative as a new bidder, there are 3 prerequisites:
+- As a Publisher, you need to have Prebid Mobile SDK integrated.
+- You need a configured Prebid Server (either self-hosted or hosted by 3rd party).
+- You need to be integrated with Ad Server SDK (e.g. Mopub) or internal product which communicates with Prebid Mobile SDK.
+
+Please see [documentation](https://developers.pubnative.net/docs/prebid-adding-pubnative-as-a-bidder) for more info.
+
+## Configuration 
+
+- bidder should be always set to "pubnative" (`imp.ext.pubnative`)
+- zone_id (int) should be always set to 1, unless special use case agreed with our account manager. (`imp.ext.pubnative.zone_id`)
+- app_auth_token (string) is unique per publisher app. Please contact our account manager to obtain yours. (`imp.ext.pubnative.app_auth_token`)
+
+An example is illustrated in a section below.
+
+## Testing
+
+Please consult with our Account Manager for testing. 
+We need to confirm that your ad request is correctly received by our system.
+
+The following test parameters can be used to verify that Prebid Server is working properly with the 
+Pubnative adapter.
+
+The following json can be used to do a request to prebid server for verifying its integration with Pubnative adapter.
+
+```json
+{
+    "id": "some-impression-id",
+    "site": {
+      "page": "https://good.site/url"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "pubnative": {
+            "zone_id": 1,
+            "app_auth_token": "b620e282f3c74787beedda34336a4821"
+          }
+        }
+      }
+    ],
+    "device": {
+      "os": "android",
+      "h": 700,
+      "w": 375
+    },
+    "tmax": 500,
+    "test": 1
+}
+```
\ No newline at end of file
diff --git a/docs/developers/add-new-bidder.md b/docs/developers/add-new-bidder.md
index e68185fd..d76a1fd2 100644
--- a/docs/developers/add-new-bidder.md
+++ b/docs/developers/add-new-bidder.md
@@ -46,6 +46,16 @@ If bidder is going to support long form video make sure bidder has:
 Note: `bid.bidVideo.PrimaryCategory` or `TypedBid.bid.Cat` should be specified.
 To learn more about IAB categories, please refer to this convenience link (not the final official definition): [IAB categories](https://adtagmacros.com/list-of-iab-categories-for-advertisement/)
 
+### Timeout notification support
+This is an optional feature. If you wish to get timeout notifications when a bid request from PBS times out, you can implement the
+`MakeTimeoutNotification` method in your adapter. If you do not wish timeout notification, do not implement the method.
+
+`func (a *Adapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error)`
+
+Here the `RequestData` supplied as an argument is the request returned from `MakeRequests` that timed out. If an adapter generates
+multiple requests, and more than one of them times out, then there will be a call to `MakeTimeoutNotification` for each failed
+request. The function should then return a `RequestData` object that will be the timeout notification to be sent to the bidder, or a list of errors encountered trying to create the timeout notification request. Timeout notifications will not generate subsequent timeout notifications if they timeout or fail.
+
 ## Test Your Bidder
 
 ### Automated Tests
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index bcb13724..bd50fca9 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -315,7 +315,12 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 	}
 
 	if err := ccpaPolicy.Validate(); err != nil {
-		errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("CCPA value is invalid and will be ignored. (%s)", err.Error())})
+		errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)})
+
+		ccpaPolicy.Value = ""
+		if err := ccpaPolicy.Write(req); err != nil {
+			errL = append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err))
+		}
 	}
 
 	impIDs := make(map[string]int, len(req.Imp))
diff --git a/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json b/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json
index 0a9fe656..d62f4043 100644
--- a/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json
+++ b/endpoints/openrtb2/sample-requests/video/video_invalid_sample.json
@@ -1,68 +1,69 @@
 {
-  "description": "Video endpoint valid request.",
+  "description": "Video endpoint valid request due to missing pods.",
 
-  "requestPayload":
-{
-  "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
-  "accountid": "555888777",
-
-  "site": {
-    "page": "prebid.com"
-  },
-  "user": {
-    "buyeruids": {
-      "appnexus": "unique_id_an",
-      "rubicon": "unique_id_rubi"
+  "requestPayload": {
+    "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
+      "ip": "123.145.167.10",
+      "devicetype": 1,
+      "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
+      "lmt": 44,
+      "os": "mac os",
+      "w": 640,
+      "h": 480,
+      "didsha1": "didsha1",
+      "didmd5": "didmd5",
+      "dpidsha1": "dpidsha1",
+      "dpidmd5": "dpidmd5",
+      "macsha1": "macsha1",
+      "macmd5": "macmd5"
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
     },
-    "gdpr": {
-      "consentrequired": false,
-      "consentstring": "something"
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
     },
-    "yob": 1991,
-    "gender": "F",
-    "keywords": "Hotels, Travelling"
-  },
-  "device11": {
-    "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
-    "ip": "123.145.167.10",
-    "devicetype": 1,
-    "dnt": 33,
-    "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
-    "lmt": 44,
-    "os": "mac os",
-    "w": 640,
-    "h": 480,
-    "didsha1": "didsha1",
-    "didmd5": "didmd5",
-    "dpidsha1": "dpidsha1",
-    "dpidmd5": "dpidmd5",
-    "macsha1": "macsha1",
-    "macmd5": "macmd5"
-  },
-  "includebrandcategory":{
-    "primaryadserver": 1,
-    "publisher": ""
-  },
-  "video": {
-    "w": 640,
-    "h": 480,
-    "mimes": [
-      "video/mp4"
-    ],
-    "protocols": [
-      2,3,5,6
-    ]
-  },
-  "content": {
-    "episode": 6,
-    "title": "episodeName",
-    "series": "TvName",
-    "season": "season3",
-    "len": 900,
-    "livestream": 0
-  },
-  "cacheconfig": {
-    "ttl": 42
+    "cacheconfig": {
+      "ttl": 42
+    }
   }
-}
 }
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample.json
index caa16f52..7ccdbf83 100644
--- a/endpoints/openrtb2/sample-requests/video/video_valid_sample.json
+++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample.json
@@ -1,85 +1,86 @@
 {
   "description": "Video endpoint valid request.",
 
-  "requestPayload":
-{
-  "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
-  "accountid": "555888777",
-  "podconfig": {
-    "durationrangesec": [
-      30
-    ],
-    "requireexactduration": true,
-    "pods": [
-      {
-        "podid": 1,
-        "adpoddurationsec": 180,
-        "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
-      },
-      {
-        "podid": 2,
-        "adpoddurationsec": 150,
-        "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+  "requestPayload": {
+    "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
+    "podconfig": {
+      "durationrangesec": [
+        30
+      ],
+      "requireexactduration": true,
+      "pods": [{
+          "podid": 1,
+          "adpoddurationsec": 180,
+          "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+        },
+        {
+          "podid": 2,
+          "adpoddurationsec": 150,
+          "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+        }
+      ]
+    },
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
       }
-    ]
-  },
-  "site": {
-    "page": "prebid.com"
-  },
-  "user": {
-    "buyeruids": {
-      "appnexus": "unique_id_an",
-      "rubicon": "unique_id_rubi"
     },
-    "gdpr": {
-      "consentrequired": false,
-      "consentstring": "something"
+    "device": {
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
+      "ip": "123.145.167.10",
+      "devicetype": 1,
+      "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
+      "lmt": 44,
+      "os": "mac os",
+      "w": 640,
+      "h": 480,
+      "didsha1": "didsha1",
+      "didmd5": "didmd5",
+      "dpidsha1": "dpidsha1",
+      "dpidmd5": "dpidmd5",
+      "macsha1": "macsha1",
+      "macmd5": "macmd5"
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
+    },
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
     },
-    "yob": 1991,
-    "gender": "F",
-    "keywords": "Hotels, Travelling"
-  },
-  "device11": {
-    "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
-    "ip": "123.145.167.10",
-    "devicetype": 1,
-    "dnt": 33,
-    "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
-    "lmt": 44,
-    "os": "mac os",
-    "w": 640,
-    "h": 480,
-    "didsha1": "didsha1",
-    "didmd5": "didmd5",
-    "dpidsha1": "dpidsha1",
-    "dpidmd5": "dpidmd5",
-    "macsha1": "macsha1",
-    "macmd5": "macmd5"
-  },
-  "includebrandcategory":{
-    "primaryadserver": 1,
-    "publisher": ""
-  },
-  "video": {
-    "w": 640,
-    "h": 480,
-    "mimes": [
-      "video/mp4"
-    ],
-    "protocols": [
-      2,3,5,6
-    ]
-  },
-  "content": {
-    "episode": 6,
-    "title": "episodeName",
-    "series": "TvName",
-    "season": "season3",
-    "len": 900,
-    "livestream": 0
-  },
-  "cacheconfig": {
-    "ttl": 42
+    "cacheconfig": {
+      "ttl": 42
+    }
   }
-}
 }
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json
new file mode 100644
index 00000000..b512c683
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_malformed.json
@@ -0,0 +1,88 @@
+{
+  "description": "Video endpoint valid request.",
+
+  "requestPayload": {
+    "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
+    "podconfig": {
+      "durationrangesec": [
+        30
+      ],
+      "requireexactduration": true,
+      "pods": [{
+          "podid": 1,
+          "adpoddurationsec": 180,
+          "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+        },
+        {
+          "podid": 2,
+          "adpoddurationsec": 150,
+          "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+        }
+      ]
+    },
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0,
+        "us_privacy": "${malformed}"
+      }
+    },
+    "user": {
+      "buyeruid": "anyId",
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
+      "ip": "123.145.167.10",
+      "devicetype": 1,
+      "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
+      "lmt": 44,
+      "os": "mac os",
+      "w": 640,
+      "h": 480,
+      "didsha1": "didsha1",
+      "didmd5": "didmd5",
+      "dpidsha1": "dpidsha1",
+      "dpidmd5": "dpidmd5",
+      "macsha1": "macsha1",
+      "macmd5": "macmd5"
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
+    },
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
+    },
+    "cacheconfig": {
+      "ttl": 42
+    }
+  }
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json
new file mode 100644
index 00000000..cfa389d4
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_ccpa_valid.json
@@ -0,0 +1,88 @@
+{
+  "description": "Video endpoint valid request.",
+
+  "requestPayload": {
+    "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
+    "podconfig": {
+      "durationrangesec": [
+        30
+      ],
+      "requireexactduration": true,
+      "pods": [{
+          "podid": 1,
+          "adpoddurationsec": 180,
+          "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+        },
+        {
+          "podid": 2,
+          "adpoddurationsec": 150,
+          "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+        }
+      ]
+    },
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0,
+        "us_privacy": "1NYN"
+      }
+    },
+    "user": {
+      "buyeruid": "anyId",
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
+      }
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
+      "ip": "123.145.167.10",
+      "devicetype": 1,
+      "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
+      "lmt": 44,
+      "os": "mac os",
+      "w": 640,
+      "h": 480,
+      "didsha1": "didsha1",
+      "didmd5": "didmd5",
+      "dpidsha1": "dpidsha1",
+      "dpidmd5": "dpidmd5",
+      "macsha1": "macsha1",
+      "macmd5": "macmd5"
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
+    },
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
+    },
+    "cacheconfig": {
+      "ttl": 42
+    }
+  }
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json
index 504af2d6..c3ad7769 100644
--- a/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json
+++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_different_durations.json
@@ -1,86 +1,87 @@
 {
-  "description": "Video endpoint valid request.",
+  "description": "Video endpoint valid request with different durations.",
 
-  "requestPayload":
-{
-  "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
-  "accountid": "555888777",
-  "podconfig": {
-    "durationrangesec": [
-      15,
-      30
-    ],
-    "requireexactduration": true,
-    "pods": [
-      {
-        "podid": 1,
-        "adpoddurationsec": 180,
-        "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
-      },
-      {
-        "podid": 2,
-        "adpoddurationsec": 150,
-        "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+  "requestPayload": {
+    "storedrequestid": "80ce30c53c16e6ede735f123ef6e32361bfc7b22",
+    "podconfig": {
+      "durationrangesec": [
+        15,
+        30
+      ],
+      "requireexactduration": true,
+      "pods": [{
+          "podid": 1,
+          "adpoddurationsec": 180,
+          "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+        },
+        {
+          "podid": 2,
+          "adpoddurationsec": 150,
+          "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+        }
+      ]
+    },
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
       }
-    ]
-  },
-  "site": {
-    "page": "prebid.com"
-  },
-  "user": {
-    "buyeruids": {
-      "appnexus": "unique_id_an",
-      "rubicon": "unique_id_rubi"
     },
-    "gdpr": {
-      "consentrequired": false,
-      "consentstring": "something"
+    "device": {
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
+      "ip": "123.145.167.10",
+      "devicetype": 1,
+      "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
+      "lmt": 44,
+      "os": "mac os",
+      "w": 640,
+      "h": 480,
+      "didsha1": "didsha1",
+      "didmd5": "didmd5",
+      "dpidsha1": "dpidsha1",
+      "dpidmd5": "dpidmd5",
+      "macsha1": "macsha1",
+      "macmd5": "macmd5"
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
+    },
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
     },
-    "yob": 1991,
-    "gender": "F",
-    "keywords": "Hotels, Travelling"
-  },
-  "device11": {
-    "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2",
-    "ip": "123.145.167.10",
-    "devicetype": 1,
-    "dnt": 33,
-    "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
-    "lmt": 44,
-    "os": "mac os",
-    "w": 640,
-    "h": 480,
-    "didsha1": "didsha1",
-    "didmd5": "didmd5",
-    "dpidsha1": "dpidsha1",
-    "dpidmd5": "dpidmd5",
-    "macsha1": "macsha1",
-    "macmd5": "macmd5"
-  },
-  "includebrandcategory":{
-    "primaryadserver": 1,
-    "publisher": ""
-  },
-  "video": {
-    "w": 640,
-    "h": 480,
-    "mimes": [
-      "video/mp4"
-    ],
-    "protocols": [
-      2,3,5,6
-    ]
-  },
-  "content": {
-    "episode": 6,
-    "title": "episodeName",
-    "series": "TvName",
-    "season": "season3",
-    "len": 900,
-    "livestream": 0
-  },
-  "cacheconfig": {
-    "ttl": 42
+    "cacheconfig": {
+      "ttl": 42
+    }
   }
-}
 }
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json
index 68c3f4e1..6a9dc605 100644
--- a/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json
+++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_with_device_user_agent.json
@@ -1,80 +1,85 @@
-
 {
-  "accountid": "555888777",
-  "podconfig": {
-    "durationrangesec": [
-      30
-    ],
-    "requireexactduration": true,
-    "pods": [
-      {
-        "podid": 1,
-        "adpoddurationsec": 180,
-        "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
-      },
-      {
-        "podid": 2,
-        "adpoddurationsec": 150,
-        "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+  "description": "Video endpoint valid request with device data.",
+
+  "requestPayload": {
+    "podconfig": {
+      "durationrangesec": [
+        30
+      ],
+      "requireexactduration": true,
+      "pods": [{
+          "podid": 1,
+          "adpoddurationsec": 180,
+          "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+        },
+        {
+          "podid": 2,
+          "adpoddurationsec": 150,
+          "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+        }
+      ]
+    },
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
       }
-    ]
-  },
-  "site": {
-    "page": "prebid.com"
-  },
-  "user": {
-    "buyeruids": {
-      "appnexus": "unique_id_an",
-      "rubicon": "unique_id_rubi"
     },
-    "gdpr": {
-      "consentrequired": false,
-      "consentstring": "something"
+    "user": {
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
+      }
+    },
+    "device": {
+      "ua": "TestHeaderSample",
+      "ip": "123.145.167.10",
+      "devicetype": 1,
+      "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
+      "lmt": 44,
+      "os": "mac os",
+      "w": 640,
+      "h": 480,
+      "didsha1": "didsha1",
+      "didmd5": "didmd5",
+      "dpidsha1": "dpidsha1",
+      "dpidmd5": "dpidmd5",
+      "macsha1": "macsha1",
+      "macmd5": "macmd5"
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
+    },
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
     },
-    "yob": 1991,
-    "gender": "F",
-    "keywords": "Hotels, Travelling"
-  },
-  "device": {
-    "ua": "TestHeaderSample",
-    "ip": "123.145.167.10",
-    "devicetype": 1,
-    "dnt": 33,
-    "ifa": "AA000DFE74168477C70D291f574D344790E0BB11",
-    "lmt": 44,
-    "os": "mac os",
-    "w": 640,
-    "h": 480,
-    "didsha1": "didsha1",
-    "didmd5": "didmd5",
-    "dpidsha1": "dpidsha1",
-    "dpidmd5": "dpidmd5",
-    "macsha1": "macsha1",
-    "macmd5": "macmd5"
-  },
-  "includebrandcategory":{
-    "primaryadserver": 1,
-    "publisher": ""
-  },
-  "video": {
-    "w": 640,
-    "h": 480,
-    "mimes": [
-      "video/mp4"
-    ],
-    "protocols": [
-      2,3,5,6
-    ]
-  },
-  "content": {
-    "episode": 6,
-    "title": "episodeName",
-    "series": "TvName",
-    "season": "season3",
-    "len": 900,
-    "livestream": 0
-  },
-  "cacheconfig": {
-    "ttl": 42
+    "cacheconfig": {
+      "ttl": 42
+    }
   }
-}
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json b/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json
index e040a562..19939186 100644
--- a/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json
+++ b/endpoints/openrtb2/sample-requests/video/video_valid_sample_without_device_user_agent.json
@@ -1,63 +1,69 @@
-
 {
-  "accountid": "555888777",
-  "podconfig": {
-    "durationrangesec": [
-      30
-    ],
-    "requireexactduration": true,
-    "pods": [
-      {
-        "podid": 1,
-        "adpoddurationsec": 180,
-        "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
-      },
-      {
-        "podid": 2,
-        "adpoddurationsec": 150,
-        "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+  "description": "Video endpoint valid request without device data.",
+
+  "requestPayload": {
+    "podconfig": {
+      "durationrangesec": [
+        30
+      ],
+      "requireexactduration": true,
+      "pods": [{
+          "podid": 1,
+          "adpoddurationsec": 180,
+          "configid": "fba10607-0c12-43d1-ad07-b8a513bc75d6"
+        },
+        {
+          "podid": 2,
+          "adpoddurationsec": 150,
+          "configid": "8b452b41-2681-4a20-9086-6f16ffad7773"
+        }
+      ]
+    },
+    "site": {
+      "page": "prebid.com"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
       }
-    ]
-  },
-  "site": {
-    "page": "prebid.com"
-  },
-  "user": {
-    "buyeruids": {
-      "appnexus": "unique_id_an",
-      "rubicon": "unique_id_rubi"
     },
-    "gdpr": {
-      "consentrequired": false,
-      "consentstring": "something"
+    "user": {
+      "yob": 1991,
+      "gender": "F",
+      "keywords": "Hotels, Travelling",
+      "ext": {
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "unique_id_an",
+            "rubicon": "unique_id_rubi"
+          }
+        }
+      }
+    },
+    "includebrandcategory": {
+      "primaryadserver": 1,
+      "publisher": ""
+    },
+    "video": {
+      "w": 640,
+      "h": 480,
+      "mimes": [
+        "video/mp4"
+      ],
+      "protocols": [
+        2, 3, 5, 6
+      ]
+    },
+    "content": {
+      "episode": 6,
+      "title": "episodeName",
+      "series": "TvName",
+      "season": "season3",
+      "len": 900,
+      "livestream": 0
     },
-    "yob": 1991,
-    "gender": "F",
-    "keywords": "Hotels, Travelling"
-  },
-  "includebrandcategory":{
-    "primaryadserver": 1,
-    "publisher": ""
-  },
-  "video": {
-    "w": 640,
-    "h": 480,
-    "mimes": [
-      "video/mp4"
-    ],
-    "protocols": [
-      2,3,5,6
-    ]
-  },
-  "content": {
-    "episode": 6,
-    "title": "episodeName",
-    "series": "TvName",
-    "season": "season3",
-    "len": 900,
-    "livestream": 0
-  },
-  "cacheconfig": {
-    "ttl": 42
+    "cacheconfig": {
+      "ttl": 42
+    }
   }
-}
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 020a5196..64c99fa5 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -204,7 +204,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	deps.setFieldsImplicitly(r, bidReq) // move after merge
 
 	errL = deps.validateRequest(bidReq)
-	if len(errL) > 0 {
+	if errortypes.ContainsFatalError(errL) {
 		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
@@ -232,7 +232,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 
 	if acctIdErr := validateAccount(deps.cfg, labels.PubID); acctIdErr != nil {
-		errL = append(errL, acctIdErr)
+		errL := []error{err}
 		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index b91f01a7..c8fbb775 100755
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -5,6 +5,8 @@ import (
 	"net/http"
 	"strings"
 
+	"github.com/prebid/prebid-server/pbsmetrics"
+
 	"github.com/prebid/prebid-server/adapters"
 	ttx "github.com/prebid/prebid-server/adapters/33across"
 	"github.com/prebid/prebid-server/adapters/adform"
@@ -29,6 +31,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
 	"github.com/prebid/prebid-server/adapters/datablocks"
+	"github.com/prebid/prebid-server/adapters/dmx"
 	"github.com/prebid/prebid-server/adapters/emx_digital"
 	"github.com/prebid/prebid-server/adapters/engagebdr"
 	"github.com/prebid/prebid-server/adapters/eplanning"
@@ -73,6 +76,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
 	"github.com/prebid/prebid-server/adapters/yeahmobi"
+	"github.com/prebid/prebid-server/adapters/yieldlab"
 	"github.com/prebid/prebid-server/adapters/yieldmo"
 	"github.com/prebid/prebid-server/adapters/yieldone"
 	"github.com/prebid/prebid-server/adapters/zeroclickfraud"
@@ -83,7 +87,7 @@ import (
 // The newAdapterMap function is segregated to its own file to make it a simple and clean location for each Adapter
 // to register itself. No wading through Exchange code to find it.
 
-func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapters.BidderInfos) map[openrtb_ext.BidderName]adaptedBidder {
+func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapters.BidderInfos, me pbsmetrics.MetricsEngine) map[openrtb_ext.BidderName]adaptedBidder {
 	ortbBidders := map[openrtb_ext.BidderName]adapters.Bidder{
 		openrtb_ext.Bidder33Across:     ttx.New33AcrossBidder(cfg.Adapters[string(openrtb_ext.Bidder33Across)].Endpoint),
 		openrtb_ext.BidderAdform:       adform.NewAdformBidder(client, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint),
@@ -106,6 +110,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderConsumable:   consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint),
 		openrtb_ext.BidderCpmstar:      cpmstar.NewCpmstarBidder(cfg.Adapters[string(openrtb_ext.BidderCpmstar)].Endpoint),
 		openrtb_ext.BidderDatablocks:   datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint),
+		openrtb_ext.BidderDmx:          dmx.NewDmxBidder(cfg.Adapters[string(openrtb_ext.BidderDmx)].Endpoint),
 		openrtb_ext.BidderEmxDigital:   emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint),
 		openrtb_ext.BidderEngageBDR:    engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint),
 		openrtb_ext.BidderEPlanning:    eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint),
@@ -153,6 +158,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderUcfunnel:         ucfunnel.NewUcfunnelBidder(cfg.Adapters[string(openrtb_ext.BidderUcfunnel)].Endpoint),
 		openrtb_ext.BidderUnruly:           unruly.NewUnrulyBidder(client, cfg.Adapters[string(openrtb_ext.BidderUnruly)].Endpoint),
 		openrtb_ext.BidderValueImpression:  valueimpression.NewValueImpressionBidder(cfg.Adapters[string(openrtb_ext.BidderValueImpression)].Endpoint),
+		openrtb_ext.BidderYieldlab:         yieldlab.NewYieldlabBidder(cfg.Adapters[string(openrtb_ext.BidderYieldlab)].Endpoint),
 		openrtb_ext.BidderVerizonMedia:     verizonmedia.NewVerizonMediaBidder(client, cfg.Adapters[string(openrtb_ext.BidderVerizonMedia)].Endpoint),
 		openrtb_ext.BidderVisx:             visx.NewVisxBidder(cfg.Adapters[string(openrtb_ext.BidderVisx)].Endpoint),
 		openrtb_ext.BidderVrtcal:           vrtcal.NewVrtcalBidder(cfg.Adapters[string(openrtb_ext.BidderVrtcal)].Endpoint),
@@ -186,7 +192,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 	for name, bidder := range ortbBidders {
 		// Clean out any disabled bidders
 		if infos[string(name)].Status == adapters.StatusActive {
-			allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client)
+			allBidders[name] = adaptBidder(adapters.EnforceBidderInfo(bidder, infos[string(name)]), client, cfg, me)
 		}
 	}
 
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 7a53db5e..f9b4a522 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -10,13 +10,18 @@ import (
 	"net/http"
 	"time"
 
+	"github.com/golang/glog"
+	"github.com/prebid/prebid-server/config/util"
+
 	"github.com/mxmCherry/openrtb"
 	nativeRequests "github.com/mxmCherry/openrtb/native/request"
 	nativeResponse "github.com/mxmCherry/openrtb/native/response"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currencies"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/pbsmetrics"
 	"golang.org/x/net/context/ctxhttp"
 )
 
@@ -82,16 +87,20 @@ type pbsOrtbSeatBid struct {
 //
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
-func adaptBidder(bidder adapters.Bidder, client *http.Client) adaptedBidder {
+func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me pbsmetrics.MetricsEngine) adaptedBidder {
 	return &bidderAdapter{
-		Bidder: bidder,
-		Client: client,
+		Bidder:      bidder,
+		Client:      client,
+		DebugConfig: cfg.Debug,
+		me:          me,
 	}
 }
 
 type bidderAdapter struct {
-	Bidder adapters.Bidder
-	Client *http.Client
+	Bidder      adapters.Bidder
+	Client      *http.Client
+	DebugConfig config.Debug
+	me          pbsmetrics.MetricsEngine
 }
 
 func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currencies.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
@@ -365,8 +374,21 @@ func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.Timeou
 		httpReq, err := http.NewRequest(toReq.Method, toReq.Uri, bytes.NewBuffer(toReq.Body))
 		if err == nil {
 			httpReq.Header = req.Headers
-			ctxhttp.Do(ctx, bidder.Client, httpReq)
-			// No validation yet on sending notifications
+			httpResp, err := ctxhttp.Do(ctx, bidder.Client, httpReq)
+			success := (err == nil && httpResp.StatusCode >= 200 && httpResp.StatusCode < 300)
+			bidder.me.RecordTimeoutNotice(success)
+			if bidder.DebugConfig.TimeoutNotification.Log && !(bidder.DebugConfig.TimeoutNotification.FailOnly && success) {
+				var msg string
+				if err == nil {
+					msg = fmt.Sprintf("TimeoutNotification: status:(%d) body:%s", httpResp.StatusCode, string(toReq.Body))
+				} else {
+					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)
+			}
+		} else {
+			bidder.me.RecordTimeoutNotice(false)
 		}
 	}
 
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 6d51b87d..660beb64 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -69,7 +69,7 @@ type bidResponseWrapper struct {
 func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine pbsmetrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currencies.RateConverter) Exchange {
 	e := new(exchange)
 
-	e.adapterMap = newAdapterMap(client, cfg, infos)
+	e.adapterMap = newAdapterMap(client, cfg, infos, metricsEngine)
 	e.cache = cache
 	e.cacheTime = time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond
 	e.me = metricsEngine
diff --git a/exchange/utils.go b/exchange/utils.go
index d961089c..f602d1e8 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -60,10 +60,12 @@ func cleanOpenRTBRequests(ctx context.Context,
 			coreBidder := resolveBidder(bidder.String(), aliases)
 
 			var publisherID = labels.PubID
-			ok, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent)
+			ok, geo, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent)
 			privacyEnforcement.GDPR = !ok && err == nil
+			privacyEnforcement.GDPRGeo = !geo && err == nil
 		} else {
 			privacyEnforcement.GDPR = false
+			privacyEnforcement.GDPRGeo = false
 		}
 
 		privacyEnforcement.Apply(bidReq, ampGDPRException)
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index 9390d942..0dfa12f5 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -23,7 +23,7 @@ type Permissions interface {
 	// Determines whether or not to send PI information to a bidder, or mask it out.
 	//
 	// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
-	PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error)
+	PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error)
 
 	// Exposes the AMP execption flag
 	AMPException() bool
diff --git a/gdpr/impl.go b/gdpr/impl.go
index 8743d7f2..60db804a 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -2,11 +2,13 @@ package gdpr
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/prebid/go-gdpr/api"
 	tcf1constants "github.com/prebid/go-gdpr/consentconstants"
 	consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2"
 	"github.com/prebid/go-gdpr/vendorconsent"
+	tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
 	"github.com/prebid/go-gdpr/vendorlist"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -40,10 +42,10 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return false, nil
 }
 
-func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
+func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
 	_, ok := p.cfg.NonStandardPublisherMap[PublisherID]
 	if ok {
-		return true, nil
+		return true, true, nil
 	}
 
 	id, ok := p.vendorIDs[bidder]
@@ -52,10 +54,10 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt
 	}
 
 	if consent == "" {
-		return p.cfg.UsersyncIfAmbiguous, nil
+		return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil
 	}
 
-	return false, nil
+	return false, false, nil
 }
 
 func (p *permissionsImpl) AMPException() bool {
@@ -78,38 +80,104 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen
 	}
 
 	// InfoStorageAccess is the same across TCF 1 and TCF 2
+	if parsedConsent.Version() == 2 {
+		if !p.cfg.TCF2.Purpose1.Enabled {
+			// We are not enforcing purpose 1
+			return true, nil
+		}
+		consent, ok := parsedConsent.(tcf2.ConsentMetadata)
+		if !ok {
+			err := fmt.Errorf("Unable to access TCF2 parsed consent")
+			return false, err
+		}
+		return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess), nil
+	}
 	if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) {
 		return true, nil
 	}
 	return false, nil
 }
 
-func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, error) {
+func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, error) {
 	// If we're not given a consent string, respect the preferences in the app config.
 	if consent == "" {
-		return p.cfg.UsersyncIfAmbiguous, nil
+		return p.cfg.UsersyncIfAmbiguous, p.cfg.UsersyncIfAmbiguous, nil
 	}
 
 	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent)
 	if err != nil {
-		return false, err
+		return false, false, err
 	}
 
 	if vendor == nil {
-		return false, nil
+		return false, false, nil
 	}
 
 	if parsedConsent.Version() == 2 {
-		// Need to add the location special purpose once the library supports it.
+		if p.cfg.TCF2.Enabled {
+			return p.allowPITCF2(parsedConsent, vendor, vendorID)
+		}
 		if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) {
-			return true, nil
+			return true, true, nil
 		}
 	} else {
 		if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) {
-			return true, nil
+			return true, true, nil
 		}
 	}
-	return false, nil
+	return false, false, nil
+}
+
+func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, err error) {
+	consent, ok := parsedConsent.(tcf2.ConsentMetadata)
+	err = nil
+	allowPI = false
+	allowGeo = false
+	if !ok {
+		err = fmt.Errorf("Unable to access TCF2 parsed consent")
+		return
+	}
+	if p.cfg.TCF2.SpecialPurpose1.Enabled {
+		allowGeo = consent.SpecialFeatureOptIn(1) && vendor.SpecialPurpose(1)
+	} else {
+		allowGeo = true
+	}
+	// Set to true so any purpose check can flip it to false
+	allowPI = true
+	if p.cfg.TCF2.Purpose1.Enabled {
+		allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess)
+	}
+	if p.cfg.TCF2.Purpose2.Enabled {
+		allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving)
+	}
+	if p.cfg.TCF2.Purpose7.Enabled {
+		allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance)
+	}
+	return
+}
+
+const pubRestrictNotAllowed = 0
+const pubRestrictRequireConsent = 1
+const pubRestrictRequireLegitInterest = 2
+
+func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose) bool {
+	if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() {
+		return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed
+	}
+	if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) {
+		return false
+	}
+	if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) {
+		return vendor.PurposeStrict(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID)
+	}
+	if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireLegitInterest, vendorID) {
+		// Need LITransparency here
+		return vendor.LegitimateInterestStrict(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID)
+	}
+	purposeAllowed := vendor.Purpose(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID)
+	legitInterest := vendor.LegitimateInterest(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID)
+
+	return purposeAllowed || legitInterest
 }
 
 func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) {
@@ -146,8 +214,8 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
 	return true, nil
 }
 
-func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
-	return true, nil
+func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
+	return true, true, nil
 }
 
 func (a AlwaysAllow) AMPException() bool {
diff --git a/go.mod b/go.mod
index 89cc69e4..0224057e 100644
--- a/go.mod
+++ b/go.mod
@@ -7,23 +7,17 @@ require (
 	github.com/DATA-DOG/go-sqlmock v1.3.0
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/OneOfOne/xxhash v1.2.5 // indirect
-	github.com/aerospike/aerospike-client-go v2.7.2+incompatible
 	github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
 	github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
 	github.com/blang/semver v3.5.1+incompatible
-	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
 	github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
 	github.com/cespare/xxhash v1.0.0 // indirect
 	github.com/chasex/glog v0.0.0-20160217080310-c62392af379c
 	github.com/coocood/freecache v1.0.1
-	github.com/didip/tollbooth v4.0.2+incompatible
 	github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5
 	github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd
-	github.com/go-redis/redis v6.15.7+incompatible
-	github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5
 	github.com/gofrs/uuid v3.2.0+incompatible
 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
-	github.com/golang/snappy v0.0.1
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/influxdata/influxdb v1.6.1 // indirect
 	github.com/julienschmidt/httprouter v1.1.0
@@ -37,9 +31,8 @@ require (
 	github.com/mxmCherry/openrtb v11.0.0+incompatible
 	github.com/onsi/ginkgo v1.10.1 // indirect
 	github.com/onsi/gomega v1.7.0 // indirect
-	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
 	github.com/pelletier/go-toml v1.2.0 // indirect
-	github.com/prebid/go-gdpr v0.7.0
+	github.com/prebid/go-gdpr v0.8.2
 	github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf
 	github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed
 	github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
@@ -48,27 +41,24 @@ require (
 	github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165
 	github.com/rs/cors v1.5.0
 	github.com/sergi/go-diff v1.0.0 // indirect
-	github.com/sirupsen/logrus v1.4.2
 	github.com/spaolacci/murmur3 v1.1.0 // indirect
 	github.com/spf13/afero v1.1.1 // indirect
 	github.com/spf13/cast v1.2.0 // indirect
 	github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 // indirect
 	github.com/spf13/pflag v1.0.2 // indirect
 	github.com/spf13/viper v1.1.0
+	github.com/stretchr/objx v0.1.1 // indirect
 	github.com/stretchr/testify v1.5.1
-	github.com/valyala/fasthttp v1.9.0
 	github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609
-	github.com/xorcare/pointer v1.1.0
 	github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	github.com/yudai/pp v2.0.1+incompatible // indirect
-	github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb // indirect
 	golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
 	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
+	golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect
 	golang.org/x/text v0.3.0
-	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	gopkg.in/yaml.v2 v2.2.2
 )
diff --git a/go.sum b/go.sum
index f929408f..5d941b89 100644
--- a/go.sum
+++ b/go.sum
@@ -6,57 +6,35 @@ github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cq
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
 github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
 github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
-github.com/aerospike/aerospike-client-go v2.7.2+incompatible h1:bWbRf8trg1FhKF7u43KLGNfOH60RlvIgQjpaS107DZ8=
-github.com/aerospike/aerospike-client-go v2.7.2+incompatible/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
-github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
-github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
-github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
-github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
-github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
 github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
 github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A=
 github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4=
 github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s=
 github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/coocood/freecache v1.0.1 h1:oFyo4msX2c0QIKU+kuMJUwsKamJ+AKc2JJrKcMszJ5M=
 github.com/coocood/freecache v1.0.1/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M=
-github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk=
 github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
-github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5 h1:ZZVxQRCm4ewuoqqLBJ7LHpsk4OGx2PkyCsRKLq4oHgE=
-github.com/gocql/gocql v0.0.0-20200203083758-81b8263d9fe5/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
 github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
-github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
-github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@@ -67,17 +45,6 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4
 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
-github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
-github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
@@ -99,14 +66,12 @@ github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
-github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prebid/go-gdpr v0.7.0 h1:m4E/FjUhTBMciDsd3lQlbzFyXLzNK+JQkFmInJpFAwc=
-github.com/prebid/go-gdpr v0.7.0/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0=
+github.com/prebid/go-gdpr v0.8.2 h1:mN2jKYZZpJkCYFQB/nDTJoPpuGYblOYP2UUzOzRggII=
+github.com/prebid/go-gdpr v0.8.2/go.mod h1:FPY0uxSrl9/Mz237LnPo3ge4aCG0wQ9FWf2b4WhwNn0=
 github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf h1:CcE+KN1tCtWKsUFH5IzdQxHIgP609VSIVe5Hywg2phs=
 github.com/prebid/prebid-cache v0.0.0-20200218152159-6d6d678c1caf/go.mod h1:k5xrl5ZpnumN1S2x8w8cMiFYsgRuVyAeFJz+BkSi+98=
 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg=
@@ -123,8 +88,6 @@ github.com/rs/cors v1.5.0 h1:dgSHE6+ia18arGOTIYQKKGWLvEbGvmbNE6NfxhoNHUY=
 github.com/rs/cors v1.5.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I=
@@ -140,16 +103,10 @@ github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
-github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
-github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 h1:Va10CytCCYRm4xBTses5ZDeDjeIQjhaiC9nRCe/yflI=
 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303/go.mod h1:Xdcad1nGVhQfhoV0go+/4WaI/RZkWlvfjkVCdpMTxPY=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
@@ -158,16 +115,12 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY=
 github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
-github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM=
-github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y=
 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M=
 github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
 github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
 github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
 github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
-github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
-github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -178,7 +131,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -186,14 +138,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/p
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index f2f8e7c6..b3ecddb0 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -46,6 +46,7 @@ const (
 	BidderConversant       BidderName = "conversant"
 	BidderCpmstar          BidderName = "cpmstar"
 	BidderDatablocks       BidderName = "datablocks"
+	BidderDmx              BidderName = "dmx"
 	BidderEmxDigital       BidderName = "emx_digital"
 	BidderEngageBDR        BidderName = "engagebdr"
 	BidderEPlanning        BidderName = "eplanning"
@@ -91,6 +92,7 @@ const (
 	BidderVisx             BidderName = "visx"
 	BidderVrtcal           BidderName = "vrtcal"
 	BidderYeahmobi         BidderName = "yeahmobi"
+	BidderYieldlab         BidderName = "yieldlab"
 	BidderYieldmo          BidderName = "yieldmo"
 	BidderYieldone         BidderName = "yieldone"
 	BidderZeroClickFraud   BidderName = "zeroclickfraud"
@@ -121,6 +123,7 @@ var BidderMap = map[string]BidderName{
 	"conversant":        BidderConversant,
 	"cpmstar":           BidderCpmstar,
 	"datablocks":        BidderDatablocks,
+	"dmx":               BidderDmx,
 	"emx_digital":       BidderEmxDigital,
 	"engagebdr":         BidderEngageBDR,
 	"eplanning":         BidderEPlanning,
@@ -166,6 +169,7 @@ var BidderMap = map[string]BidderName{
 	"visx":              BidderVisx,
 	"vrtcal":            BidderVrtcal,
 	"yeahmobi":          BidderYeahmobi,
+	"yieldlab":          BidderYieldlab,
 	"yieldmo":           BidderYieldmo,
 	"yieldone":          BidderYieldone,
 	"zeroclickfraud":    BidderZeroClickFraud,
diff --git a/openrtb_ext/imp_yieldlab.go b/openrtb_ext/imp_yieldlab.go
new file mode 100644
index 00000000..604b7e8c
--- /dev/null
+++ b/openrtb_ext/imp_yieldlab.go
@@ -0,0 +1,10 @@
+package openrtb_ext
+
+// ExtImpYieldlab defines the contract for bidrequest.imp[i].ext.yieldlab
+type ExtImpYieldlab struct {
+	AdslotID  string            `json:"adslotId"`
+	SupplyID  string            `json:"supplyId"`
+	AdSize    string            `json:"adSize"`
+	Targeting map[string]string `json:"targeting"`
+	ExtId     string            `json:"extId"`
+}
diff --git a/pbsmetrics/config/metrics.go b/pbsmetrics/config/metrics.go
index e1cdaceb..4e249785 100644
--- a/pbsmetrics/config/metrics.go
+++ b/pbsmetrics/config/metrics.go
@@ -188,6 +188,13 @@ func (me *MultiMetricsEngine) RecordRequestQueueTime(success bool, requestType p
 	}
 }
 
+// RecordTimeoutNotice across all engines
+func (me *MultiMetricsEngine) RecordTimeoutNotice(success bool) {
+	for _, thisME := range *me {
+		thisME.RecordTimeoutNotice(success)
+	}
+}
+
 // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests)
 type DummyMetricsEngine struct{}
 
@@ -262,3 +269,7 @@ func (me *DummyMetricsEngine) RecordPrebidCacheRequestTime(success bool, length
 // RecordRequestQueueTime as a noop
 func (me *DummyMetricsEngine) RecordRequestQueueTime(success bool, requestType pbsmetrics.RequestType, length time.Duration) {
 }
+
+// RecordTimeoutNotice as a noop
+func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) {
+}
diff --git a/pbsmetrics/go_metrics.go b/pbsmetrics/go_metrics.go
index ff3d9681..1ced4d57 100644
--- a/pbsmetrics/go_metrics.go
+++ b/pbsmetrics/go_metrics.go
@@ -48,6 +48,9 @@ type Metrics struct {
 	ImpsTypeAudio  metrics.Meter
 	ImpsTypeNative metrics.Meter
 
+	TimeoutNotificationSuccess metrics.Meter
+	TimeoutNotificationFailure metrics.Meter
+
 	AdapterMetrics map[openrtb_ext.BidderName]*AdapterMetrics
 	// Don't export accountMetrics because we need helper functions here to insure its properly populated dynamically
 	accountMetrics        map[string]*accountMetrics
@@ -131,6 +134,9 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa
 		ImpsTypeAudio:  blankMeter,
 		ImpsTypeNative: blankMeter,
 
+		TimeoutNotificationSuccess: blankMeter,
+		TimeoutNotificationFailure: blankMeter,
+
 		AdapterMetrics:  make(map[openrtb_ext.BidderName]*AdapterMetrics, len(exchanges)),
 		accountMetrics:  make(map[string]*accountMetrics),
 		MetricsDisabled: disableMetrics,
@@ -209,6 +215,9 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d
 
 	newMetrics.userSyncSet[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.sets", registry)
 	newMetrics.userSyncGDPRPrevent[unknownBidder] = metrics.GetOrRegisterMeter("usersync.unknown.gdpr_prevent", registry)
+
+	newMetrics.TimeoutNotificationSuccess = metrics.GetOrRegisterMeter("timeout_notification.ok", registry)
+	newMetrics.TimeoutNotificationFailure = metrics.GetOrRegisterMeter("timeout_notification.failed", registry)
 	return newMetrics
 }
 
@@ -544,6 +553,15 @@ func (me *Metrics) RecordRequestQueueTime(success bool, requestType RequestType,
 
 }
 
+func (me *Metrics) RecordTimeoutNotice(success bool) {
+	if success {
+		me.TimeoutNotificationSuccess.Mark(1)
+	} else {
+		me.TimeoutNotificationFailure.Mark(1)
+	}
+	return
+}
+
 func doMark(bidder openrtb_ext.BidderName, meters map[openrtb_ext.BidderName]metrics.Meter) {
 	met, ok := meters[bidder]
 	if ok {
diff --git a/pbsmetrics/metrics.go b/pbsmetrics/metrics.go
index 611692c9..e65ba313 100644
--- a/pbsmetrics/metrics.go
+++ b/pbsmetrics/metrics.go
@@ -275,4 +275,5 @@ type MetricsEngine interface {
 	RecordStoredImpCacheResult(cacheResult CacheResult, inc int)
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
+	RecordTimeoutNotice(sucess bool)
 }
diff --git a/pbsmetrics/metrics_mock.go b/pbsmetrics/metrics_mock.go
index 1f5b84b1..482cbf24 100644
--- a/pbsmetrics/metrics_mock.go
+++ b/pbsmetrics/metrics_mock.go
@@ -101,3 +101,8 @@ func (me *MetricsEngineMock) RecordPrebidCacheRequestTime(success bool, length t
 func (me *MetricsEngineMock) RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) {
 	me.Called(success, requestType, length)
 }
+
+// RecordTimeoutNotice mock
+func (me *MetricsEngineMock) RecordTimeoutNotice(success bool) {
+	me.Called(success)
+}
diff --git a/pbsmetrics/prometheus/prometheus.go b/pbsmetrics/prometheus/prometheus.go
index d66defea..e385b044 100644
--- a/pbsmetrics/prometheus/prometheus.go
+++ b/pbsmetrics/prometheus/prometheus.go
@@ -28,6 +28,7 @@ type Metrics struct {
 	requestsWithoutCookie        *prometheus.CounterVec
 	storedImpressionsCacheResult *prometheus.CounterVec
 	storedRequestCacheResult     *prometheus.CounterVec
+	timeout_notifications        *prometheus.CounterVec
 
 	// Adapter Metrics
 	adapterBids          *prometheus.CounterVec
@@ -79,6 +80,11 @@ const (
 	requestRejectLabel  = "requestRejectedLabel"
 )
 
+const (
+	requestSuccessful = "ok"
+	requestFailed     = "failed"
+)
+
 // NewMetrics initializes a new Prometheus metrics instance with preloaded label values.
 func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 	requestTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}
@@ -147,6 +153,11 @@ func NewMetrics(cfg config.PrometheusMetrics) *Metrics {
 		"Count of stored request cache requests attempts by hits or miss.",
 		[]string{cacheResultLabel})
 
+	metrics.timeout_notifications = newCounter(cfg, metrics.Registry,
+		"timeout_notification",
+		"Count of timeout notifications triggered, and if they were successfully sent.",
+		[]string{successLabel})
+
 	metrics.adapterBids = newCounter(cfg, metrics.Registry,
 		"adapter_bids",
 		"Count of bids labeled by adapter and markup delivery type (adm or nurl).",
@@ -398,3 +409,15 @@ func (m *Metrics) RecordRequestQueueTime(success bool, requestType pbsmetrics.Re
 		requestStatusLabel: successLabelFormatted,
 	}).Observe(length.Seconds())
 }
+
+func (m *Metrics) RecordTimeoutNotice(success bool) {
+	if success {
+		m.timeout_notifications.With(prometheus.Labels{
+			successLabel: requestSuccessful,
+		}).Inc()
+	} else {
+		m.timeout_notifications.With(prometheus.Labels{
+			successLabel: requestFailed,
+		}).Inc()
+	}
+}
diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go
index 64579f2a..11ac4345 100644
--- a/privacy/ccpa/policy.go
+++ b/privacy/ccpa/policy.go
@@ -14,7 +14,7 @@ type Policy struct {
 	Value string
 }
 
-// ReadPolicy extracts the CCPA regulation policy from an OpenRTB regs ext.
+// ReadPolicy extracts the CCPA regulation policy from an OpenRTB request.
 func ReadPolicy(req *openrtb.BidRequest) (Policy, error) {
 	policy := Policy{}
 
@@ -32,6 +32,10 @@ func ReadPolicy(req *openrtb.BidRequest) (Policy, error) {
 // Write mutates an OpenRTB bid request with the context of the CCPA policy.
 func (p Policy) Write(req *openrtb.BidRequest) error {
 	if p.Value == "" {
+		return clearPolicy(req)
+	}
+
+	if req == nil {
 		return nil
 	}
 
@@ -59,6 +63,37 @@ func (p Policy) Write(req *openrtb.BidRequest) error {
 	return err
 }
 
+func clearPolicy(req *openrtb.BidRequest) error {
+	if req == nil {
+		return nil
+	}
+
+	if req.Regs == nil {
+		return nil
+	}
+
+	if len(req.Regs.Ext) == 0 {
+		return nil
+	}
+
+	var extMap map[string]interface{}
+	err := json.Unmarshal(req.Regs.Ext, &extMap)
+	if err == nil {
+		delete(extMap, "us_privacy")
+		if len(extMap) == 0 {
+			req.Regs.Ext = nil
+		} else {
+			ext, err := json.Marshal(extMap)
+			if err == nil {
+				req.Regs.Ext = ext
+			}
+			return err
+		}
+	}
+
+	return err
+}
+
 // Validate returns an error if the CCPA policy does not adhere to the IAB spec.
 func (p Policy) Validate() error {
 	if err := ValidateConsent(p.Value); err != nil {
diff --git a/privacy/enforcement.go b/privacy/enforcement.go
index 96d03ef4..d302192e 100644
--- a/privacy/enforcement.go
+++ b/privacy/enforcement.go
@@ -6,14 +6,15 @@ import (
 
 // Enforcement represents the privacy policies to enforce for an OpenRTB bid request.
 type Enforcement struct {
-	CCPA  bool
-	COPPA bool
-	GDPR  bool
+	CCPA    bool
+	COPPA   bool
+	GDPR    bool
+	GDPRGeo bool
 }
 
 // Any returns true if at least one privacy policy requires enforcement.
 func (e Enforcement) Any() bool {
-	return e.CCPA || e.COPPA || e.GDPR
+	return e.CCPA || e.COPPA || e.GDPR || e.GDPRGeo
 }
 
 // Apply cleans personally identifiable information from an OpenRTB bid request.
@@ -45,7 +46,7 @@ func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo {
 		return ScrubStrategyGeoFull
 	}
 
-	if e.GDPR || e.CCPA {
+	if e.GDPRGeo || e.CCPA {
 		return ScrubStrategyGeoReducedPrecision
 	}
 
@@ -60,5 +61,11 @@ func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUs
 	if e.GDPR && ampGDPRException {
 		return ScrubStrategyUserNone
 	}
-	return ScrubStrategyUserID
+
+	// If no user scrubbing is needed, then return none, else scrub ID (COPPA checked above)
+	if e.CCPA || e.GDPR {
+		return ScrubStrategyUserID
+	}
+
+	return ScrubStrategyUserNone
 }
diff --git a/privacy/scrubber.go b/privacy/scrubber.go
index ec313e91..65543683 100644
--- a/privacy/scrubber.go
+++ b/privacy/scrubber.go
@@ -1,6 +1,7 @@
 package privacy
 
 import (
+	"encoding/json"
 	"strings"
 
 	"github.com/mxmCherry/openrtb"
@@ -104,11 +105,13 @@ func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo Sc
 	case ScrubStrategyUserIDAndDemographic:
 		userCopy.BuyerUID = ""
 		userCopy.ID = ""
+		userCopy.Ext = scrubUserExtIDs(userCopy.Ext)
 		userCopy.Yob = 0
 		userCopy.Gender = ""
 	case ScrubStrategyUserID:
 		userCopy.BuyerUID = ""
 		userCopy.ID = ""
+		userCopy.Ext = scrubUserExtIDs(userCopy.Ext)
 	}
 
 	switch geo {
@@ -179,3 +182,29 @@ func scrubGeoPrecision(geo *openrtb.Geo) *openrtb.Geo {
 	geoCopy.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0 // Round Longitude
 	return &geoCopy
 }
+
+func scrubUserExtIDs(userExt json.RawMessage) json.RawMessage {
+	if len(userExt) == 0 {
+		return userExt
+	}
+
+	var userExtParsed map[string]json.RawMessage
+	err := json.Unmarshal(userExt, &userExtParsed)
+	if err != nil {
+		return userExt
+	}
+
+	_, hasEids := userExtParsed["eids"]
+	_, hasDigitrust := userExtParsed["digitrust"]
+	if hasEids || hasDigitrust {
+		delete(userExtParsed, "eids")
+		delete(userExtParsed, "digitrust")
+
+		result, err := json.Marshal(userExtParsed)
+		if err == nil {
+			return result
+		}
+	}
+
+	return userExt
+}
diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml
new file mode 100644
index 00000000..d6e54178
--- /dev/null
+++ b/static/bidder-info/dmx.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: "steve@districtm.net"
+capabilities:
+  site:
+    mediaTypes:
+      - banner
+      - video
+  app:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-info/yieldlab.yaml b/static/bidder-info/yieldlab.yaml
new file mode 100644
index 00000000..654e6c74
--- /dev/null
+++ b/static/bidder-info/yieldlab.yaml
@@ -0,0 +1,11 @@
+maintainer:
+  email: "solutions@yieldlab.de"
+capabilities:
+  site:
+    mediaTypes:
+      - banner
+      - video
+  app:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-params/dmx.json b/static/bidder-params/dmx.json
new file mode 100644
index 00000000..4c0df65e
--- /dev/null
+++ b/static/bidder-params/dmx.json
@@ -0,0 +1,22 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "District M DMX Adapter Params",
+  "description": "A schema which validates params accepted by the DMX adapter",
+  "type": "object",
+  "properties": {
+    "memberid" : {
+      "type": "string",
+      "description": "Represent boost MemberId from districtm UI"
+    },
+    "tagid": {
+      "type": "string",
+      "description": "Represent the placement ID, this value is optional"
+    },
+    "bidfloor": {
+      "type": "string",
+      "description": "The minimum price acceptable for a bid"
+    }
+  },
+
+  "required": ["memberid"]
+}
\ No newline at end of file
diff --git a/static/bidder-params/yieldlab.json b/static/bidder-params/yieldlab.json
new file mode 100644
index 00000000..900d65da
--- /dev/null
+++ b/static/bidder-params/yieldlab.json
@@ -0,0 +1,33 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Yieldlab Adapter Params",
+  "description": "A schema which validates params accepted by the Yieldlab adapter",
+  "type": "object",
+  "properties": {
+    "adslotId": {
+      "type": "string",
+      "description": "Yieldlab ID of the ad slot"
+    },
+    "supplyId": {
+      "type": "string",
+      "description": "Yieldlab ID of the supply"
+    },
+    "adSize": {
+      "type": "string",
+      "description": "Size of the adslot in pixel, e.g. 200x50"
+    },
+    "extId": {
+      "type": "string",
+      "description": "External ID used for reporting"
+    },
+    "targeting": {
+      "type": "object",
+      "description": "Targeting information in key value pairs"
+    }
+  },
+  "required": [
+    "adslotId",
+    "supplyId",
+    "adSize"
+  ]
+}
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 3f12ee7f..5dccf855 100755
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -24,6 +24,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
 	"github.com/prebid/prebid-server/adapters/datablocks"
+	"github.com/prebid/prebid-server/adapters/dmx"
 	"github.com/prebid/prebid-server/adapters/emx_digital"
 	"github.com/prebid/prebid-server/adapters/engagebdr"
 	"github.com/prebid/prebid-server/adapters/eplanning"
@@ -61,6 +62,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/verizonmedia"
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
+	"github.com/prebid/prebid-server/adapters/yieldlab"
 	"github.com/prebid/prebid-server/adapters/yieldmo"
 	"github.com/prebid/prebid-server/adapters/yieldone"
 	"github.com/prebid/prebid-server/adapters/zeroclickfraud"
@@ -93,6 +95,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderDmx, dmx.NewDmxSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer)
@@ -131,6 +134,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldmo, yieldmo.NewYieldmoSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldone, yieldone.NewYieldoneSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderZeroClickFraud, zeroclickfraud.NewZeroClickFraudSyncer)


Test patch:
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index 7602ab16..8fdb9c5d 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -208,7 +208,7 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [
 	t.Helper()
 
 	if len(expected) != len(actual) {
-		t.Fatalf("%s had wrong error count. Expected %d, got %d", description, len(expected), len(actual))
+		t.Fatalf("%s had wrong error count. Expected %d, got %d (%v)", description, len(expected), len(actual), actual)
 	}
 	for i := 0; i < len(actual); i++ {
 		if expected[i].Comparison == "literal" {
diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go
index 63655da6..b8894c5e 100644
--- a/adapters/adtelligent/adtelligent_test.go
+++ b/adapters/adtelligent/adtelligent_test.go
@@ -7,5 +7,5 @@ import (
 )
 
 func TestJsonSamples(t *testing.T) {
-	adapterstest.RunJSONBidderTest(t, "adtelligenttest", NewAdtelligentBidder("http://hb.adtelligent.com/auction"))
+	adapterstest.RunJSONBidderTest(t, "adtelligenttest", NewAdtelligentBidder("http://ghb.adtelligent.com/pbs/ortb"))
 }
diff --git a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json
index 67ad2fd2..553ec618 100644
--- a/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json
+++ b/adapters/adtelligent/adtelligenttest/exemplary/media-type-mapping.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://hb.adtelligent.com/auction?aid=1000",
+        "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
         "body": {
           "id": "test-request-id",
           "imp": [
diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json
index 6648229d..a06477b4 100644
--- a/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json
+++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-banner.json
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://hb.adtelligent.com/auction?aid=1000",
+        "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
         "body": {
           "id": "test-request-id",
           "imp": [
diff --git a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json
index 97769651..f108cc94 100644
--- a/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json
+++ b/adapters/adtelligent/adtelligenttest/exemplary/simple-video.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://hb.adtelligent.com/auction?aid=1000",
+        "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
         "body": {
           "id": "test-request-id",
           "imp": [
diff --git a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json
index 9dc279bc..6155e9bc 100644
--- a/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json
+++ b/adapters/adtelligent/adtelligenttest/supplemental/explicit-dimensions.json
@@ -25,7 +25,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://hb.adtelligent.com/auction?aid=1000",
+        "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
         "body": {
           "id": "test-request-id",
           "imp": [
diff --git a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json
index 94df34af..2e5aeff3 100644
--- a/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json
+++ b/adapters/adtelligent/adtelligenttest/supplemental/wrong-impression-mapping.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://hb.adtelligent.com/auction?aid=1000",
+        "uri": "http://ghb.adtelligent.com/pbs/ortb?aid=1000",
         "body": {
           "id": "test-request-id",
           "imp": [
diff --git a/adapters/beachfront/usersync_test.go b/adapters/beachfront/usersync_test.go
index f0c4b381..0267ac05 100644
--- a/adapters/beachfront/usersync_test.go
+++ b/adapters/beachfront/usersync_test.go
@@ -30,6 +30,6 @@ func TestBeachfrontSyncer(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "https://sync.bfmio.com/sync_s2s?gdpr=A&us_privacy=C&url=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%5Bio_cid%5D", syncInfo.URL)
 	assert.Equal(t, "iframe", syncInfo.Type)
-	assert.EqualValues(t, 0, syncer.GDPRVendorID())
+	assert.EqualValues(t, uint16(335), syncer.GDPRVendorID())
 	assert.Equal(t, false, syncInfo.SupportCORS)
 }
diff --git a/adapters/dmx/dmx_test.go b/adapters/dmx/dmx_test.go
new file mode 100644
index 00000000..e9f195eb
--- /dev/null
+++ b/adapters/dmx/dmx_test.go
@@ -0,0 +1,782 @@
+package dmx
+
+import (
+	"encoding/json"
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"strings"
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+var (
+	bidRequest string
+)
+
+func TestFetchParams(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+	var arrImp []openrtb.Imp
+	var imps = fetchParams(
+		dmxExt{Bidder: dmxParams{
+			TagId:       "222",
+			PublisherId: "5555",
+		}},
+		openrtb.Imp{ID: "32"},
+		openrtb.Imp{ID: "32"},
+		arrImp,
+		&openrtb.Banner{W: &width, H: &height, Format: []openrtb.Format{
+			{W: 300, H: 250},
+		}},
+		nil,
+		1)
+	var imps2 = fetchParams(
+		dmxExt{Bidder: dmxParams{
+			DmxId:    "222",
+			MemberId: "5555",
+		}},
+		openrtb.Imp{ID: "32"},
+		openrtb.Imp{ID: "32"},
+		arrImp,
+		&openrtb.Banner{W: &width, H: &height, Format: []openrtb.Format{
+			{W: 300, H: 250},
+		}},
+		nil,
+		1)
+	if len(imps) == 0 {
+		t.Errorf("should increment the length by one")
+	}
+
+	if len(imps2) == 0 {
+		t.Errorf("should increment the length by one")
+	}
+
+}
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "dmxtest", new(DmxAdapter))
+}
+
+func TestMakeRequestsOtherPlacement(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		User: &openrtb.User{ID: "bscakucbkasucbkasunscancasuin"},
+		Imp:  []openrtb.Imp{imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		ID: "1234",
+	}
+
+	actualAdapterRequests, err := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if actualAdapterRequests == nil {
+		t.Errorf("request should be nil")
+	}
+	if len(err) != 0 {
+		t.Errorf("We should have no error")
+	}
+
+}
+
+func TestMakeRequestsInvalid(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		ID: "1234",
+	}
+
+	actualAdapterRequests, err := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if len(actualAdapterRequests) != 0 {
+		t.Errorf("request should be nil")
+	}
+	if len(err) == 0 {
+		t.Errorf("We should have no error")
+	}
+
+}
+
+func TestMakeRequestNoSite(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1},
+		App: &openrtb.App{ID: "cansanuabnua", Publisher: &openrtb.Publisher{ID: "whatever"}},
+		ID:  "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("openrtb type should be an Array when it's an App")
+	}
+	var the_body openrtb.BidRequest
+	if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil {
+		t.Errorf("failed to read bid request")
+	}
+
+	if the_body.App == nil {
+		t.Errorf("app property should be populated")
+	}
+
+	if the_body.App.Publisher.ID == "" {
+		t.Errorf("Missing publisher ID must be in")
+	}
+}
+
+func TestMakeRequestsApp(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		App: &openrtb.App{ID: "cansanuabnua", Publisher: &openrtb.Publisher{ID: "whatever"}},
+		ID:  "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("openrtb type should be an Array when it's an App")
+	}
+	var the_body openrtb.BidRequest
+	if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil {
+		t.Errorf("failed to read bid request")
+	}
+
+	if the_body.App == nil {
+		t.Errorf("app property should be populated")
+	}
+
+}
+
+func TestMakeRequestsNoUser(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		ID: "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if actualAdapterRequests != nil {
+		t.Errorf("openrtb type should be empty")
+	}
+
+}
+
+func TestMakeRequests(t *testing.T) {
+	//server := httptest.NewServer(http.HandlerFunc(DummyDmxServer))
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+	imp2 := openrtb.Imp{
+		ID:  "imp2",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+	imp3 := openrtb.Imp{
+		ID:  "imp3",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1, imp2, imp3},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{ID: "districtmID"},
+		ID:   "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("should have 1 request")
+	}
+	var the_body openrtb.BidRequest
+	if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil {
+		t.Errorf("failed to read bid request")
+	}
+
+	if len(the_body.Imp) != 3 {
+		t.Errorf("must have 3 bids")
+	}
+
+}
+
+func TestMakeBidVideo(t *testing.T) {
+	var w, h int = 640, 480
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Video: &openrtb.Video{
+			W:     width,
+			H:     height,
+			MIMEs: []string{"video/mp4"},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{ID: "districtmID"},
+		ID:   "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("should have 1 request")
+	}
+	var the_body openrtb.BidRequest
+	if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil {
+		t.Errorf("failed to read bid request")
+	}
+
+	if len(the_body.Imp) != 1 {
+		t.Errorf("must have 1 bids")
+	}
+}
+
+func TestMakeBidsNoContent(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{ID: "districtmID"},
+		ID:   "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+
+	_, err204 := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], &adapters.ResponseData{StatusCode: 204})
+
+	if err204 != nil {
+		t.Errorf("Was expecting nil")
+	}
+
+	_, err400 := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], &adapters.ResponseData{StatusCode: 400})
+
+	if err400 == nil {
+		t.Errorf("Was expecting error")
+	}
+
+	_, err500 := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], &adapters.ResponseData{StatusCode: 500})
+
+	if err500 == nil {
+		t.Errorf("Was expecting error")
+	}
+
+	bidResponse := &adapters.ResponseData{
+		StatusCode: 200,
+		Body: []byte(`{
+  "id": "JdSgvXjee0UZ",
+  "seatbid": [
+    {
+      "bid": [
+        {
+          "id": "16-40dbf1ef_0gKywr9JnzPAW4bE-1",
+          "impid": "imp1",
+          "price": 2.3456,
+          "adm": "<some html here \/>",
+          "nurl": "dmxnotificationurlhere",
+          "adomain": [
+            "brand.com",
+            "advertiser.net"
+          ],
+          "cid": "12345",
+          "crid": "232303",
+          "cat": [
+            "IAB20-3"
+          ],
+          "attr": [
+            2
+          ],
+          "w": 300,
+          "h": 600,
+          "language": "en"
+        }
+      ],
+    "seat": "10001"
+    }
+  ],
+  "cur": "USD"
+}`),
+	}
+
+	bidResponseNoMatch := &adapters.ResponseData{
+		StatusCode: 200,
+		Body: []byte(`{
+  "id": "JdSgvXjee0UZ",
+  "seatbid": [
+    {
+      "bid": [
+        {
+          "id": "16-40dbf1ef_0gKywr9JnzPAW4bE-1",
+          "impid": "djvnsvns",
+          "price": 2.3456,
+          "adm": "<some html here \/>",
+          "nurl": "dmxnotificationurlhere",
+          "adomain": [
+            "brand.com",
+            "advertiser.net"
+          ],
+          "cid": "12345",
+          "crid": "232303",
+          "cat": [
+            "IAB20-3"
+          ],
+          "attr": [
+            2
+          ],
+          "w": 300,
+          "h": 600,
+          "language": "en"
+        }
+      ],
+    "seat": "10001"
+    }
+  ],
+  "cur": "USD"
+}`),
+	}
+
+	bids, _ := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], bidResponse)
+	if bids == nil {
+		t.Errorf("ads not parse")
+	}
+	bidsNoMatching, _ := adapter.MakeBids(&inputRequest, actualAdapterRequests[0], bidResponseNoMatch)
+	if bidsNoMatching == nil {
+		t.Errorf("ads not parse")
+	}
+
+}
+func TestUserExtEmptyObject(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1, imp1, imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{Ext: json.RawMessage(`{}`)},
+		ID:   "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+	if len(actualAdapterRequests) != 0 {
+		t.Errorf("should have 0 request")
+	}
+}
+func TestUserEidsOnly(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1, imp1, imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{Ext: json.RawMessage(`{"eids": [{
+                "source": "adserver.org",
+                "uids": [{
+                    "id": "111111111111",
+                    "ext": {
+                        "rtiPartner": "TDID"
+                    }
+                }]
+            },{
+                "source": "netid.de",
+                "uids": [{
+                    "id": "11111111"
+                }]
+            }]
+            }`)},
+		ID: "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("should have 1 request")
+	}
+}
+
+func TestUserDigitrustOnly(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1, imp1, imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{Ext: json.RawMessage(`{
+            "digitrust": {
+                "id": "11111111111",
+                "keyv": 4
+            }}`)},
+		ID: "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("should have 1 request")
+	}
+}
+
+func TestUsersEids(t *testing.T) {
+	var w, h int = 300, 250
+
+	var width, height uint64 = uint64(w), uint64(h)
+
+	adapter := NewDmxBidder("https://dmx.districtm.io/b/v2")
+	imp1 := openrtb.Imp{
+		ID:  "imp1",
+		Ext: json.RawMessage("{\"bidder\":{\"dmxid\": \"1007\", \"memberid\": \"123456\", \"seller_id\":\"1008\"}}"),
+		Banner: &openrtb.Banner{
+			W: &width,
+			H: &height,
+			Format: []openrtb.Format{
+				{W: 300, H: 250},
+			},
+		}}
+
+	inputRequest := openrtb.BidRequest{
+		Imp: []openrtb.Imp{imp1, imp1, imp1},
+		Site: &openrtb.Site{
+			Publisher: &openrtb.Publisher{
+				ID: "10007",
+			},
+		},
+		User: &openrtb.User{ID: "districtmID", Ext: json.RawMessage(`{"eids": [{
+                "source": "adserver.org",
+                "uids": [{
+                    "id": "111111111111",
+                    "ext": {
+                        "rtiPartner": "TDID"
+                    }
+                }]
+            },{
+                "source": "pubcid.org",
+                "uids": [{
+                    "id":"11111111"
+                }]
+            },
+            {
+                "source": "id5-sync.com",
+                "uids": [{
+                    "id": "ID5-12345"
+                }]
+            },
+            {
+                "source": "parrable.com",
+                "uids": [{
+                    "id": "01.1563917337.test-eid"
+                }]
+            },{
+                "source": "identityLink",
+                "uids": [{
+                    "id": "11111111"
+                }]
+            },{
+                "source": "criteo",
+                "uids": [{
+                    "id": "11111111"
+                }]
+            },{
+                "source": "britepool.com",
+                "uids": [{
+                    "id": "11111111"
+                }]
+            },{
+                "source": "liveintent.com",
+                "uids": [{
+                    "id": "11111111"
+                }]
+            },{
+                "source": "netid.de",
+                "uids": [{
+                    "id": "11111111"
+                }]
+            }],
+            "digitrust": {
+                "id": "11111111111",
+                "keyv": 4
+            }}`)},
+		ID: "1234",
+	}
+
+	actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{})
+	if len(actualAdapterRequests) != 1 {
+		t.Errorf("should have 1 request")
+	}
+	var the_body openrtb.BidRequest
+	if err := json.Unmarshal(actualAdapterRequests[0].Body, &the_body); err != nil {
+		t.Errorf("failed to read bid request")
+	}
+
+	if len(the_body.Imp) != 3 {
+		t.Errorf("must have 3 bids")
+	}
+}
+func TestVideoImpInsertion(t *testing.T) {
+	var bidResp openrtb.BidResponse
+	var bid openrtb.Bid
+	payload := []byte(`{
+    "id": "some-request-id",
+    "seatbid": [
+        {
+            "bid": [
+                {
+                    "id": "video1",
+                    "impid": "video1",
+                    "price": 5.01,
+                    "nurl": "https://demo.arripiblik.com/359585167267151",
+                    "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\"><Ad id=\"5f3c0f61_1aoDbsYiNHbYYpJ2qPtKgERbGiH\"><Wrapper><AdSystem>BidSwitch</AdSystem><VASTAdTagURI><![CDATA[https://bid.g.doubleclick.net/dbm/vast?dbm_c=AKAmf-Bsi1tzgaMFvkO8rVFA2uRjshf-8MKbfsVGvhoqjxCwhfgWnsfVqpOaKDWTYGa5YSdXijhgb8o2TGCuSC2sqawX0WP-Fw&dbm_d=AKAmf-B7vNPyDI7QTdv8f2N0jQJ9hMssfJqj7g1dhwwGRPxcWD8AbrxPgDmysYMj6IOE719Jb9IfX5eUQ7M9cki4w7q3XEI1L1AsUAZYc-HOzPIRQnOOTzypKOmzyfTjoC-r1KNgDeUPv1Z6g4BK6RH9vmRm0ML6wj4S04oJjZDzTE71QGWMZSfAhQhMDzlnSsj0JI1ruWhr_yGER4Qt61oAHC4aUnHWb7V2c7m0Z80mFoDtasDsEjF-QUJrA9LTg_taatSS1emAT2SpSyM98e_66b4YE6dOJG4hqWHl1QAsiDNQOsXZ_oEAGVdnXgS_3VIhdmHy2svbTTtvlzoGTj7tRCrDnmKdJq5dcDZgsZfCeNDBeQQXAzV1L824G5B7MyHKPHksgakRqbAr6y2-VPUuL0eeFGERNBAzQp6r2i6D3w9a3JrsdWPUw_j-6ph9Qw5T-tx8cbZ5zYH1a1RlAIYXuH15Wg-lofEOLFAud59ASP0El_xZK99fcbqcqjqAbaAkzLoTADGRv6ZYZj4wXirZ6R0PAFX4PtXawiRDt9e1oxnweT5_BKx77DtKe21yxGc2QEsQmxmxzSusDYxiCuZuWh7m1Pzp3_WsRTHKEl8T0KZAwtJDn5GjuaR_wxpztusRBtakyhb25e8xICiJvynehLCiTC3jEF5us6_M-y1RD6i3J6VJ5idGBqvYnNk2K_pwsKc0zRbC1hmSNp35xkccXFz9wK1acUq5dEUnRK--49OsfydcdfOKlxMHAbMgB4LJ3RpweBMNt1eeeKNrFRdFDXIYamkdWbr19QhAMjHghibLg6zfgxpxe2Ee0-yQqXx2Dp4pEc-11NUzoIvZuuYIj65YFPXAh9eYU3k8V6iGJcYpoac5nKdZI6qZipNWQ6ZktnUTJ60XR_wk1MlSgVJaGMlagtzuFGDL0joKYDoU898nea406NrqLUsCznXLxBqCmTXtxFZuCDTJ_kwMsyg1J5l8Jbi7McCUsIAI9jm_JhCnIAFE8x_mAANEgPW2KpVKsZP2OufTizw-8ZLS-djLzB-9EMKkOnT8EB6A--PwXyJkQUTqifgfRsg_W3bnVAX2NwpjSC-NuZ2tPIO83O_2M3EctszXJe0A-7cPmhJ68U6LDNm-SFvan4C5gnvi0eHMOJDtMGbCRCy8hqi0VIlU2IZkaL7D8TwAUDFp8zL0fABsnwLUe-14arGfs_4NIIZoJdQXoBG9uz0e6sLY4RjcjK_s60weaoBYxO8kSL-3TsrD-oMVJrCA4NOjnGxobu4H-kYqDkW4bNjA3yQ0LQVQuJUw6RUBZXTSz_9IleIQSey4060dlwA1O1zESsb3oQGa3S7E25Lof3noX5Gq2SwfT8JEoVUI03ORusmqMrnvgqlNP_kq0MPUlb_zzssb5V3jlLVsu9V8svhTIo5nz6zF-Ydp6pqn68bT-ohQzyLPJLJEm1CBEwOvur2TmmyQ-W7MriJCm_uUYfRJGJCXw44whnFKbp3CZeUmFHWLK4lauabyo3csEE5DJkMClctxMxSRp6uZOyiYlOqi8S2YJBvL_NEppzrN2W1cfKEQyNIFj1WsGx62tPY-TzOgArnEetNS3mUHQi5F1Vz6QUdmuIKQpseDqU_GVhAOVCTeh6MNpjM59SXmMFY4ram7U6WZTIniRjX27m-XVaAmzqKH-s0GlmvpO4Fssb9wQc7jHDcqXcsyqbhDTrlIuezBMxjfykPDi2hs96PRv4N5ADvEuKAPkVAKZ2pbt6lr1pZLnSFjXxIlLYXpULQwObVxcdbt0Sw4m6aHtBTyvJnz6bS7z4wpbW87My7swTWwqtBdhEo_jm4_ZHph0FB9_kw46lrWtAIk_qU6AOoE2lAQ8VrRXAOn9LUVeu5JuLCnuVNvMygzMRMn5KvUjGuxMtD53xTcAXgmbd_5Q0eryr4lMKlDFop2Djei9pyxJR1rTQ4xTfHG3kB-MgdyGZLuFuHtILkPjuVQXhNCuBFMe6dRAtuDvptk1y8VAULdda0YNDLo960PxeTFjbqrRz5wfeUqnu1FV6h9cERcJBGSZlAPH254XNcCwwYyyYr0oD2yQZxmSlr2slQpaW-XdwdGie3vlrmYhfe4-5VrDMHymfKIM-ZUuD3sq6V03Efg14JDMghu9IHO0xbefQuTFBCen1AHBVy4mwJm0Kxp6jEgdNXb7Taa0AF2zwzORu0W0P8lTtWFv3Pg5JWplYorfdJSQGr7hDuxLpyeRiJjzft9liSp22APvHumvJCNgNe0DrWddzW0sRMZOLAFQn1F8cQwLX7mIFEplD3MEQht2HYe4gR8sN44lHQw6CmYV0Ai_tjL1oS17BShey-JkVSPdWVp9r3VfNV1releGX3u-12HtheEvOSsv7qQxIU30Ui10QjnSony-ORz48c4_wbhGmucwUx37Ggf5XhmvVjJUEMDlhYtCj2-rtik2-nskOeSNYKo9hb0DPcahsBEiL4vz7fhgEiivFV0FxiHq0pEukcmOFgE6V7X_upwKli65YEOxrH_GZn2DmjjcUJHD0UNLHOe4D7DV16K7YC9HfodCtzeX0LWZnMCKXv76UiXy_sVITPm4G6Td4AXliC2zwiI_URnQqdCyhRIA40E2qjU31ED56DpkOSo1WiU4fv2mv-n5lyb99lzL2srVV0KFYcvwDOyOnNByXmFw18w6-9snWvYtCDY_AbWtvlSI_60OxCOxipqxGVvA4HXWlFUvOl3OtqRf6apMZIaEpFDcL4N0NGuItEKRb04ZE6lv1zNOxC9WBQHZV_pqK9RQ7YbKrWfcL_c79XQji0Bd3mE6k08AfsjYugcWy-wsjmq467VhLWe4OcSAB8B2Qq_eZl5RPrM6LayPWpuz637TeHNfMwuta9R394K36rAtWRf3Ns_oDJz6g&cid=CAASEuRoF_6hDi9IvsHr8we-jyLVhw]]></VASTAdTagURI><Error><![CDATA[https://gce-sc.bidswitch.net/vast_error/O2lw3vm7UGHsHYVBXTjm9Y-DOn5osUxLug71FGBtENsDQCc-y60Y47eIRTQ6TYpRGzvXKzk3o6ZC1FRWXCtQvUdjR-4JrsHILqJGIQjWxB8dGjZlUsDqwpLZNdcECrS16f-XsuNngtxHNKHFdGQ-tfvCihmYUYAPTATvkDSVXDLrwPLAIbEC11ElAmyUUlFqqYMFHvu71ibAo55IU_pQCfvScb0WcKunBti5lcbnSaOny5VE6Kc2fwHXAzG2Tbu4wyRxenfkeMPAaq5aGvIFL2fWlgz9_Y7tUaUq-k6YZxzlTJ3QbwnmbvL3LejwjkG6BnhR5DuJ-X_EWhKWmna0YsxXA-vnFBmqcH6pyuKQ5C93ZApV8xq_N86vccZ2nVTOI6DLL-8N8cVBKvmp2lL0vAPLU2A5uEaaoX8xuw322lv8ksG1McwGwlFoaWkzjBsJivY2eNeFxXCsFBF6BYiBKLCWy62iecTnQfJykTx3orDsnQez89JPW52DQSsxSb0_YPpUpfclYrBe_FAMle4AOBpZs8ib7hsGXxNOkgJdtwz8_bIIlv-U4Hmoym9ulx0svFV78boErVYrNBf6D43tHl027bfkZgXC0CsVpdmZKUHSpcI3mBQKPC5Qb-tCgVpsLK4xUMvFvJFLXIX1yjj062J4ZH9fqm3kNZdfESq4XIVOlx7aMYoDxxnBrSjthW0KKvruGzicYm7c8Nwp19xjTQwR83Cks2FPzxbAm3jAmP0vaNhS7_xkBpN_nVvxis18qaY1YYt4KQAqtxr90Y9rG0wOcmQQf52sbXzd4DqVs3H4PM4v_ZmqJOSFpvDczzKPasn0mV43TyaG-UOnkX1nFqXjMnty8-gpGPqkO5RyhKfP1vTQxpVDI_VaEiOPdNCUwXmhoxJJophsVX4FNBg_kwF-FL2d59j2Oi6gBPASSZ94dr8CMgRC1YZ_SRLwr90h/]]></Error><Impression><![CDATA[https://gce-sc.bidswitch.net/imp/1.9432/BSWhttps_A_B_Badx.g.doubleclick.net_Bpagead_Badview_Cai_RCuMI3Z7KdXrLWMIK5jAan44DIDI3a871cpZKH-u8K7u__b7-kYEAEgg__3mH2DJ3oqLwKTYD8gBBagDAcgDE5gEAKoE4wFP0EQmjUWxRmz76rmwUIT95PKyz7RN57CiJGB-sRzdZcyC4Y-6HUFDwGRTcvbxghpdmUAckrP4ZCE8BqxDB1S1GfTJ0RedqQ__jF1fZSAbUS__sk29Fo0aVpGDQHonn9DXIHSQyqD6KjtPGBdK2zNBgGcCet519-DXoAADunurUqLNITnWZbV__-W3GHtah6ZJB4grkIP3pZqbvCc7p__No0or0BTzAsyVKwm5CUZ6DfDm-GCy3V6SLjPVNtFoo6E89XbxIlSqsXWb8T__YtxVd9k-4at6Qt1sk8jx0SIvu5rcTbeoZ__8AElq-XqskC4AQDiAXksbqxIJIFBggDEAIYAZIFBggbEAIYAZIFCggiEAMYAUjPrVOSBQYIHRAEGAGSBQYIHRABGAGSBQYIHhABGAGQBgGgBk-AB8afu7cBqAeOzhuoB9XJG6gHk9gbqAe6BqgH8NkbqAfy2RuoB-zVG6gHpr4bqAfs1RvYBwDyBwkQ6J17GOTk3WbSCAcIgGEQARgf8ggXYmlkZGVyLWRpc3RyaWN0bV8xNzM2MzSACgTICwGwE9mS0QjIE8jXmAjQEwDYEwqIFALYFAE_Jsigh_Rw8Q7gjefARc_Jcmd_RChdjYS1wdWItNzM1MDg5NzEzODA5OTk1OBAAGAE_Jpr_R38_A_I_WAUCTION__PRICE_X_Jcid_RCAASEuRoF__6hDi9IvsHr8we-jyLVhw_Jtpd_RAGWhJmvwUOaWQX5xLWpPeUyjI1k5ciEvQjY2Irxt7p0U1kq77g/O2lw3vm7UGHsHYVBXTjm9Y-DOn5osUxLug71FGBtENsDQCc-y60Y47eIRTQ6TYpRGzvXKzk3o6ZC1FRWXCtQvUdjR-4JrsHILqJGIQjWxB8dGjZlUsDqwpLZNdcECrS16f-XsuNngtxHNKHFdGQ-tfvCihmYUYAPTATvkDSVXDLrwPLAIbEC11ElAmyUUlFqqYMFHvu71ibAo55IU_pQCfvScb0WcKunBti5lcbnSaOny5VE6Kc2fwHXAzG2Tbu4wyRxenfkeMPAaq5aGvIFL2fWlgz9_Y7tUaUq-k6YZxzlTJ3QbwnmbvL3LejwjkG6BnhR5DuJ-X_EWhKWmna0YsxXA-vnFBmqcH6pyuKQ5C93ZApV8xq_N86vccZ2nVTOI6DLL-8N8cVBKvmp2lL0vAPLU2A5uEaaoX8xuw322lv8ksG1McwGwlFoaWkzjBsJivY2eNeFxXCsFBF6BYiBKLCWy62iecTnQfJykTx3orDsnQez89JPW52DQSsxSb0_YPpUpfclYrBe_FAMle4AOBpZs8ib7hsGXxNOkgJdtwz8_bIIlv-U4Hmoym9ulx0svFV78boErVYrNBf6D43tHl027bfkZgXC0CsVpdmZKUHSpcI3mBQKPC5Qb-tCgVpsLK4xUMvFvJFLXIX1yjj062J4ZH9fqm3kNZdfESq4XIVOlx7aMYoDxxnBrSjthW0KKvruGzicYm7c8Nwp19xjTQwR83Cks2FPzxbAm3jAmP0vaNhS7_xkBpN_nVvxis18qaY1YYt4KQAqtxr90Y9rG0wOcmQQf52sbXzd4DqVs3H4PM4v_ZmqJOSFpvDczzKPasn0mV43TyaG-UOnkX1nFqXjMnty8-gpGPqkO5RyhKfP1vTQxpVDI_VaEiOPdNCUwXmhoxJJophsVX4FNBg_kwF-FL2d59j2Oi6gBPASSZ94dr8CMgRC1YZ_SRLwr90h/]]></Impression><Impression><![CDATA[https://us-east-sync.bidswitch.net/sync_cors?ssp=districtm&dsp_id=16&imp=1]]></Impression><Creatives></Creatives></Wrapper></Ad></VAST><!-- DMX - seat 10009 - crid 16_215446116 -->",
+                    "crid": "76575664756",
+                    "dealid": "dmx-deal-hp-24",
+                    "w": 640,
+                    "h": 480,
+                    "ext": {
+                        "prebid": {
+                            "type": "video"
+                        }
+                    }
+                },
+                {
+                    "id": "some-impression-id",
+                    "impid": "some-impression-id",
+                    "price": 5.01,
+                    "adm": "<img src='https://via.placeholder.com/300x250.png?text=dmx+2.0+300x250' height='250' width='300'/>",
+                    "crid": "1346943998",
+                    "dealid": "dmx-deal-hp-24",
+                    "w": 300,
+                    "h": 250,
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                },
+                {
+                    "id": "some-impression-id2",
+                    "impid": "some-impression-id2",
+                    "price": 5.01,
+                    "adm": "<img src='https://via.placeholder.com/728x90.png?text=dmx+2.0+728x90' height='90' width='728'/>",
+                    "crid": "1424798162",
+                    "dealid": "dmx-deal-hp-24",
+                    "w": 728,
+                    "h": 90,
+                    "ext": {
+                        "prebid": {
+                            "type": "banner"
+                        }
+                    }
+                }
+            ],
+            "seat": "dmx"
+        }
+    ]
+}`)
+
+	err := json.Unmarshal(payload, &bidResp)
+	if err != nil {
+		t.Errorf("Payload is invalid")
+	}
+	bid = openrtb.Bid(bidResp.SeatBid[0].Bid[0])
+	data := videoImpInsertion(&bid)
+	find := strings.Index(data, "demo.arripiblik.com")
+	if find == -1 {
+		t.Errorf("String was not found")
+	}
+
+}
diff --git a/adapters/dmx/dmxtest/exemplary/simple-app.json b/adapters/dmx/dmxtest/exemplary/simple-app.json
new file mode 100644
index 00000000..a2d57163
--- /dev/null
+++ b/adapters/dmx/dmxtest/exemplary/simple-app.json
@@ -0,0 +1,138 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "app":{
+      "bundle":"302324249",
+      "id":"ed6207cefff74c14878963566683c070",
+      "name":"Skout - iOS Match Buy",
+      "publisher":{
+        "id":"10400"
+      },
+      "storeurl":"https://itunes.apple.com/app/id302324249"
+    },
+    "imp": [
+      {
+
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "dmxid": "123454",
+            "publisher_id": "10400"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "",
+        "body": {
+          "id": "test-request-id",
+          "app":{
+            "bundle":"302324249",
+            "id":"ed6207cefff74c14878963566683c070",
+            "name":"Skout - iOS Match Buy",
+            "publisher":{
+              "id":"10400"
+            },
+            "storeurl":"https://itunes.apple.com/app/id302324249"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "123454",
+              "secure": 1,
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "publisher_id": "10400",
+                  "dmxid": "123454"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [{
+                "id": "7706636740145184841",
+                "impid": "test-imp-id",
+                "price": 1.75,
+                "adid": "29681110",
+                "adm": "<div>banner-ads</div>",
+                "adomain": ["dmx.districtm.io"],
+                "iurl": "https://dmx.districtm.io/b/v2",
+                "cid": "958",
+                "crid": "29681110",
+                "h": 250,
+                "w": 300
+              }]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 1.75,
+            "adm": "<div>banner-ads</div>",
+            "adid": "29681110",
+            "adomain": ["dmx.districtm.io"],
+            "iurl": "https://dmx.districtm.io/b/v2",
+            "cid": "958",
+            "crid": "29681110",
+            "w": 300,
+            "h": 250
+
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/dmx/dmxtest/exemplary/simple-banner.json b/adapters/dmx/dmxtest/exemplary/simple-banner.json
new file mode 100644
index 00000000..03ea6246
--- /dev/null
+++ b/adapters/dmx/dmxtest/exemplary/simple-banner.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "user": {
+      "id": "fhacacnasicnaic"
+    },
+    "imp": [
+      {
+
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "dmxid": "123454",
+            "publisher_id": "10400"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "",
+        "body": {
+          "id": "test-request-id",
+          "user": {
+            "id": "fhacacnasicnaic"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "123454",
+              "secure": 1,
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "publisher_id": "10400",
+                  "dmxid": "123454"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [{
+                "id": "7706636740145184841",
+                "impid": "test-imp-id",
+                "price": 1.75,
+                "adid": "29681110",
+                "adm": "<div>banner-ads</div>",
+                "adomain": ["dmx.districtm.io"],
+                "iurl": "https://dmx.districtm.io/b/v2",
+                "cid": "958",
+                "crid": "29681110",
+                "h": 250,
+                "w": 300
+              }]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 1.75,
+            "adm": "<div>banner-ads</div>",
+            "adid": "29681110",
+            "adomain": ["dmx.districtm.io"],
+            "iurl": "https://dmx.districtm.io/b/v2",
+            "cid": "958",
+            "crid": "29681110",
+            "w": 300,
+            "h": 250
+
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/dmx/dmxtest/exemplary/simple-video.json b/adapters/dmx/dmxtest/exemplary/simple-video.json
new file mode 100644
index 00000000..b4c53188
--- /dev/null
+++ b/adapters/dmx/dmxtest/exemplary/simple-video.json
@@ -0,0 +1,112 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "user": {
+      "id": "whateveryouwant"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "mimes": ["video/mp4"],
+          "minduration": 15,
+          "maxduration": 30,
+          "protocols": [2, 3, 5, 6, 7, 8],
+          "w": 940,
+          "h": 560
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "12345",
+            "publisher_id": "10400"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "",
+        "body": {
+          "id": "test-request-id",
+          "user": {
+            "id": "whateveryouwant"
+          },
+          "imp": [
+            {
+              "ext": {
+                "bidder": {
+                  "tagid": "12345",
+                  "publisher_id": "10400"
+                }
+              },
+              "id": "test-imp-id",
+              "tagid": "12345",
+              "secure": 1,
+              "video": {
+                "mimes": ["video/mp4"],
+                "minduration": 15,
+                "maxduration": 30,
+                "protocols": [2, 3, 5, 6, 7, 8],
+                "w": 940,
+                "h": 560
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [{
+                "id": "7706636740145184841",
+                "impid": "test-imp-id",
+                "price": 1.90,
+                "adid": "29681110",
+                "adm": "ads",
+                "adomain": ["dmx.districtm.io"],
+                "iurl": "https://dmx.districtm.io/b/v2",
+                "cid": "958",
+                "crid": "29681110",
+                "h": 250,
+                "w": 300
+              }]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 1.90,
+            "adm": "ads",
+            "adid": "29681110",
+            "adomain": ["dmx.districtm.io"],
+            "iurl": "https://dmx.districtm.io/b/v2",
+            "cid": "958",
+            "crid": "29681110",
+            "w": 300,
+            "h": 250
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/dmx/dmxtest/params/race/banner.json b/adapters/dmx/dmxtest/params/race/banner.json
new file mode 100644
index 00000000..1c0adff7
--- /dev/null
+++ b/adapters/dmx/dmxtest/params/race/banner.json
@@ -0,0 +1,4 @@
+{
+  "tagid": "25251",
+  "publisher_id": "100152"
+}
\ No newline at end of file
diff --git a/adapters/dmx/dmxtest/params/race/video.json b/adapters/dmx/dmxtest/params/race/video.json
new file mode 100644
index 00000000..3bbd83bd
--- /dev/null
+++ b/adapters/dmx/dmxtest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+  "tagid": "25255",
+  "publisher_id": "100151"
+}
\ No newline at end of file
diff --git a/adapters/dmx/usersync_test.go b/adapters/dmx/usersync_test.go
new file mode 100644
index 00000000..e4e3c7d8
--- /dev/null
+++ b/adapters/dmx/usersync_test.go
@@ -0,0 +1,20 @@
+package dmx
+
+import (
+	"github.com/prebid/prebid-server/privacy"
+	"testing"
+	"text/template"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDmxSyncer(t *testing.T) {
+	temp := template.Must(template.New("sync-template").Parse("https://dmx.districtm.io/s/v1/img/s/10007"))
+	syncer := NewDmxSyncer(temp)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{})
+	assert.NoError(t, err)
+	assert.Equal(t, "https://dmx.districtm.io/s/v1/img/s/10007", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.EqualValues(t, 144, syncer.GDPRVendorID())
+	assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/eplanning/eplanning_test.go b/adapters/eplanning/eplanning_test.go
index d2c331d4..28fdf6c4 100644
--- a/adapters/eplanning/eplanning_test.go
+++ b/adapters/eplanning/eplanning_test.go
@@ -8,7 +8,7 @@ import (
 )
 
 func TestJsonSamples(t *testing.T) {
-	eplanningAdapter := NewEPlanningBidder(new(http.Client), "https://ads.us.e-planning.net/hb/1")
+	eplanningAdapter := NewEPlanningBidder(new(http.Client), "http://rtb.e-planning.net/pbs/1")
 	eplanningAdapter.testing = true
 	adapterstest.RunJSONBidderTest(t, "eplanningtest", eplanningAdapter)
 }
diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json
index e602877f..55683121 100644
--- a/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json
+++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner-2.json
@@ -28,7 +28,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&r=pbs&ur=FILE",
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE",
         "body": {}
       },
       "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json
index 0403e59a..04eca985 100644
--- a/adapters/eplanning/eplanningtest/exemplary/simple-banner.json
+++ b/adapters/eplanning/eplanningtest/exemplary/simple-banner.json
@@ -31,7 +31,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadun_itco_de%3A600x300&ip=123.123.123.123&ncb=1&r=pbs&uid=2154987&ur=FILE",
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadun_itco_de%3A600x300&ip=123.123.123.123&ncb=1&uid=2154987&ur=FILE",
         "body": {}
       },
       "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/exemplary/two-banners.json b/adapters/eplanning/eplanningtest/exemplary/two-banners.json
index 8c4ca021..72aeb64b 100644
--- a/adapters/eplanning/eplanningtest/exemplary/two-banners.json
+++ b/adapters/eplanning/eplanningtest/exemplary/two-banners.json
@@ -39,7 +39,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300%2B300x250%3A300x250&ip=123.123.123.123&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300%2B300x250%3A300x250&ip=123.123.123.123&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json
index e6e25566..413c973d 100644
--- a/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json
+++ b/adapters/eplanning/eplanningtest/supplemental/app-domain-and-url-correctly-parsed.json
@@ -39,7 +39,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/mx.com.xeu/ROS?app=1&appid=%5Ba-f0-9%5D%7B16%7D&appn=MobileExchange&e=testadunitcode%3A600x300&ifa=3B8E2335-Z049&ip=123.123.123.123&ncb=1&r=pbs",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/mx.com.xeu/ROS?app=1&appid=%5Ba-f0-9%5D%7B16%7D&appn=MobileExchange&e=testadunitcode%3A600x300&ifa=3B8E2335-Z049&ip=123.123.123.123&ncb=1",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json
index f1bc29e1..05547d81 100644
--- a/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-no-size-sends-1x1.json
@@ -19,7 +19,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcodenosize%3A1x1&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcodenosize%3A1x1&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json
index 978989e2..57048882 100644
--- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json
+++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-no-bids.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json
index 7198f4ee..4ba2d44b 100644
--- a/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json
+++ b/adapters/eplanning/eplanningtest/supplemental/invalid-response-unmarshall-error.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json
index 938ede62..9e8eae8c 100644
--- a/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json
+++ b/adapters/eplanning/eplanningtest/supplemental/server-bad-request.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json
index eaa2a677..08f46d9e 100644
--- a/adapters/eplanning/eplanningtest/supplemental/server-error-code.json
+++ b/adapters/eplanning/eplanningtest/supplemental/server-error-code.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json
index d1feb865..20a2b1cf 100644
--- a/adapters/eplanning/eplanningtest/supplemental/server-no-content.json
+++ b/adapters/eplanning/eplanningtest/supplemental/server-no-content.json
@@ -21,7 +21,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=FILE",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=testadunitcode%3A600x300&ncb=1&ur=FILE",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json
index 2f43b9aa..62890d91 100644
--- a/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json
+++ b/adapters/eplanning/eplanningtest/supplemental/site-domain-and-url-correctly-parsed.json
@@ -25,7 +25,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "https://ads.us.e-planning.net/hb/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&r=pbs&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere",
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/www.publisher.com/ROS?e=testadunitcode%3A600x300&ncb=1&ur=http%3A%2F%2Fwww.publisher.com%2Fawesome%2Fsite%3Fwith%3Dsome%26parameters%3Dhere",
           "body": {}
         },
         "mockResponse": {
diff --git a/adapters/telaria/telariatest/exemplary/video-app.json b/adapters/telaria/telariatest/exemplary/video-app.json
index 09bcf998..fa755cc9 100644
--- a/adapters/telaria/telariatest/exemplary/video-app.json
+++ b/adapters/telaria/telariatest/exemplary/video-app.json
@@ -51,7 +51,6 @@
         "Content-Type": ["application/json;charset=utf-8"],
         "Accept": ["application/json"],
         "X-Openrtb-Version": ["2.5"],
-        "Accept-Encoding": ["gzip"],
         "User-Agent": ["test-user-agent"],
         "X-Forwarded-For": ["123.123.123.123"],
         "Accept-Language": ["en"],
diff --git a/adapters/telaria/telariatest/exemplary/video-web.json b/adapters/telaria/telariatest/exemplary/video-web.json
index 5dc26b0f..c671d2f5 100644
--- a/adapters/telaria/telariatest/exemplary/video-web.json
+++ b/adapters/telaria/telariatest/exemplary/video-web.json
@@ -45,7 +45,6 @@
         "Content-Type": ["application/json;charset=utf-8"],
         "Accept": ["application/json"],
         "X-Openrtb-Version": ["2.5"],
-        "Accept-Encoding": ["gzip"],
         "User-Agent": ["test-user-agent"],
         "X-Forwarded-For": ["123.123.123.123"],
         "Accept-Language": ["en"],
diff --git a/adapters/telaria/telariatest/supplemental/invalid-response.json b/adapters/telaria/telariatest/supplemental/invalid-response.json
index 8e4f1ee8..3f614a07 100644
--- a/adapters/telaria/telariatest/supplemental/invalid-response.json
+++ b/adapters/telaria/telariatest/supplemental/invalid-response.json
@@ -45,7 +45,6 @@
         "Content-Type": ["application/json;charset=utf-8"],
         "Accept": ["application/json"],
         "X-Openrtb-Version": ["2.5"],
-        "Accept-Encoding": ["gzip"],
         "User-Agent": ["test-user-agent"],
         "X-Forwarded-For": ["123.123.123.123"],
         "Accept-Language": ["en"],
diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go
new file mode 100644
index 00000000..8c230c15
--- /dev/null
+++ b/adapters/yieldlab/params_test.go
@@ -0,0 +1,63 @@
+package yieldlab
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/yieldlab.json
+//
+// These also validate the format of the external API: request.imp[i].ext.yieldlab
+
+// TestValidParams makes sure that the yieldlab schema accepts all imp.ext fields which we intend to support.
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderYieldlab, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected yieldlab params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the yieldlab schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderYieldlab, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"adslotId": "123","supplyId":"23456","adSize":"100x100"}`,
+	`{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf"}`,
+	`{"adslotId": "123","supplyId":"23456","adSize":"100x100","extId":"asdf","targeting":{"a":"b"}}`,
+	`{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`,
+	`{"adslotId": "123","supplyId":"23456","adSize":"100x100","targeting":{"a":"b"}}`,
+}
+
+var invalidParams = []string{
+	`{"supplyId":"23456","adSize":"100x100"}`,
+	`{"adslotId": "123","adSize":"100x100","extId":"asdf"}`,
+	`{"adslotId": "123","supplyId":"23456","extId":"asdf","targeting":{"a":"b"}}`,
+	`{"adslotId": "123","supplyId":"23456"}`,
+	`{"adSize":"100x100","supplyId":"23456"}`,
+	`{"adslotId": "123","adSize":"100x100"}`,
+	`{"supplyId":"23456"}`,
+	`{"adslotId": "123"}`,
+	`{}`,
+	`[]`,
+	`{"a":"b"}`,
+	`null`,
+}
diff --git a/adapters/yieldlab/usersync_test.go b/adapters/yieldlab/usersync_test.go
new file mode 100644
index 00000000..3892c16b
--- /dev/null
+++ b/adapters/yieldlab/usersync_test.go
@@ -0,0 +1,26 @@
+package yieldlab
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+)
+
+func TestYieldlabSyncer(t *testing.T) {
+	temp := template.Must(template.New("sync-template").Parse("https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25YL_UID%25%25"))
+	syncer := NewYieldlabSyncer(temp)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal: "0",
+		},
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%25%25YL_UID%25%25", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.EqualValues(t, 70, syncer.GDPRVendorID())
+	assert.False(t, syncInfo.SupportCORS)
+}
diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go
new file mode 100644
index 00000000..b6ca0507
--- /dev/null
+++ b/adapters/yieldlab/yieldlab_test.go
@@ -0,0 +1,128 @@
+package yieldlab
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+const testURL = "https://ad.yieldlab.net/testing/"
+
+var testCacheBuster cacheBuster = func() string {
+	return "testing"
+}
+
+var testWeekGenerator weekGenerator = func() string {
+	return "33"
+}
+
+func newTestYieldlabBidder(endpoint string) *YieldlabAdapter {
+	return &YieldlabAdapter{
+		endpoint:    endpoint,
+		cacheBuster: testCacheBuster,
+		getWeek:     testWeekGenerator,
+	}
+}
+
+func TestNewYieldlabBidder(t *testing.T) {
+	bid := NewYieldlabBidder(testURL)
+	assert.NotNil(t, bid)
+	assert.Equal(t, bid.endpoint, testURL)
+	assert.NotNil(t, bid.cacheBuster)
+	assert.NotNil(t, bid.getWeek)
+}
+
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "yieldlabtest", newTestYieldlabBidder(testURL))
+}
+
+func Test_splitSize(t *testing.T) {
+	type args struct {
+		size string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    uint64
+		want1   uint64
+		wantErr bool
+	}{
+		{
+			name: "valid",
+			args: args{
+				size: "300x800",
+			},
+			want:    300,
+			want1:   800,
+			wantErr: false,
+		},
+		{
+			name: "empty",
+			args: args{
+				size: "",
+			},
+			want:    0,
+			want1:   0,
+			wantErr: false,
+		},
+		{
+			name: "invalid",
+			args: args{
+				size: "test",
+			},
+			want:    0,
+			want1:   0,
+			wantErr: false,
+		},
+		{
+			name: "invalid_height",
+			args: args{
+				size: "200xtest",
+			},
+			want:    0,
+			want1:   0,
+			wantErr: true,
+		},
+		{
+			name: "invalid_width",
+			args: args{
+				size: "testx200",
+			},
+			want:    0,
+			want1:   0,
+			wantErr: true,
+		},
+		{
+			name: "invalid_separator",
+			args: args{
+				size: "200y200",
+			},
+			want:    0,
+			want1:   0,
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, got1, err := splitSize(tt.args.size)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("splitSize() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("splitSize() got = %v, want %v", got, tt.want)
+			}
+			if got1 != tt.want1 {
+				t.Errorf("splitSize() got1 = %v, want %v", got1, tt.want1)
+			}
+		})
+	}
+}
+
+func TestYieldlabAdapter_makeEndpointURL_invalidEndpoint(t *testing.T) {
+	bid := NewYieldlabBidder("test$:/something§")
+	_, err := bid.makeEndpointURL(nil, nil)
+	assert.Error(t, err)
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/banner.json b/adapters/yieldlab/yieldlabtest/exemplary/banner.json
new file mode 100644
index 00000000..8dd94404
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/banner.json
@@ -0,0 +1,111 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "adSize": "728x90",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        }
+      }
+    ],
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c"
+    },
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "Referer": [
+            "http://localhost:9090/gdpr.html"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "adm": "<script src=\"https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing\"></script>",
+            "crid": "12345123433",
+            "dealid": "1234",
+            "id": "12345",
+            "impid": "test-imp-id",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json
new file mode 100644
index 00000000..381ba688
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/gdpr.json
@@ -0,0 +1,119 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "adSize": "728x90",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1
+      }
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    },
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c",
+      "ext": {
+        "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "Referer": [
+            "http://localhost:9090/gdpr.html"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "adm": "<script src=\"https://ad.yieldlab.net/d/12345/123456789/728x90?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&gdpr=1&id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing\"></script>",
+            "crid": "12345123433",
+            "dealid": "1234",
+            "id": "12345",
+            "impid": "test-imp-id",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video.json b/adapters/yieldlab/yieldlabtest/exemplary/video.json
new file mode 100644
index 00000000..9e970ae7
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/video.json
@@ -0,0 +1,136 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "adSize": "728x90",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        },
+        "video": {
+          "context": "instream",
+          "mimes": [
+            "video/mp4"
+          ],
+          "playerSize": [
+            [
+              400,
+              600
+            ]
+          ],
+          "minduration": 1,
+          "maxduration": 2,
+          "protocols": [
+            1,
+            2
+          ],
+          "w": 1,
+          "h": 2,
+          "startdelay": 1,
+          "placement": 1,
+          "playbackmethod": [
+            2
+          ]
+        }
+      }
+    ],
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c"
+    },
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "Referer": [
+            "http://localhost:9090/gdpr.html"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "crid": "12345123433",
+            "dealid": "1234",
+            "id": "12345",
+            "impid": "test-imp-id",
+            "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/video_app.json b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json
new file mode 100644
index 00000000..67d526b3
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/video_app.json
@@ -0,0 +1,136 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "adSize": "728x90",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        },
+        "video": {
+          "context": "instream",
+          "mimes": [
+            "video/mp4"
+          ],
+          "playerSize": [
+            [
+              400,
+              600
+            ]
+          ],
+          "minduration": 1,
+          "maxduration": 2,
+          "protocols": [
+            1,
+            2
+          ],
+          "w": 1,
+          "h": 2,
+          "startdelay": 1,
+          "placement": 1,
+          "playbackmethod": [
+            2
+          ]
+        }
+      }
+    ],
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?content=json&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pubappname=Awesome+App&pubbundlename=com.app.awesome&pvid=true&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "crid": "12345123433",
+            "dealid": "1234",
+            "id": "12345",
+            "impid": "test-imp-id",
+            "nurl": "https://ad.yieldlab.net/d/12345/123456789/728x90?id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/config/config_test.go b/config/config_test.go
index 92794d79..ee8e68e7 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -426,6 +426,14 @@ func TestCookieSizeError(t *testing.T) {
 	}
 }
 
+func TestValidateDebug(t *testing.T) {
+	cfg := newDefaultConfig(t)
+	cfg.Debug.TimeoutNotification.SamplingRate = 1.1
+
+	err := cfg.validate()
+	assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed")
+}
+
 func newDefaultConfig(t *testing.T) *Configuration {
 	v := viper.New()
 	SetupViper(v, "")
diff --git a/config/util/loggers_test.go b/config/util/loggers_test.go
new file mode 100644
index 00000000..4bfab967
--- /dev/null
+++ b/config/util/loggers_test.go
@@ -0,0 +1,32 @@
+package util
+
+import (
+	"bytes"
+	"fmt"
+	"math/rand"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLogRandomSample(t *testing.T) {
+
+	const expected string = `This is test line 2
+This is test line 3
+`
+
+	myRand := rand.New(rand.NewSource(1337))
+	var buf bytes.Buffer
+
+	mylogger := func(msg string, args ...interface{}) {
+		buf.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...))
+	}
+
+	logRandomSampleImpl("This is test line 1", mylogger, 0.5, myRand.Float32)
+	logRandomSampleImpl("This is test line 2", mylogger, 0.5, myRand.Float32)
+	logRandomSampleImpl("This is test line 3", mylogger, 0.5, myRand.Float32)
+	logRandomSampleImpl("This is test line 4", mylogger, 0.5, myRand.Float32)
+	logRandomSampleImpl("This is test line 5", mylogger, 0.5, myRand.Float32)
+
+	assert.EqualValues(t, expected, buf.String())
+}
diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go
index 3035a6d4..5e9e9639 100644
--- a/endpoints/auction_test.go
+++ b/endpoints/auction_test.go
@@ -407,6 +407,7 @@ type auctionMockPermissions struct {
 	allowBidderSync  bool
 	allowHostCookies bool
 	allowPI          bool
+	allowGeo         bool
 }
 
 func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
@@ -417,8 +418,8 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o
 	return m.allowBidderSync, nil
 }
 
-func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
-	return m.allowPI, nil
+func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
+	return m.allowPI, m.allowGeo, nil
 }
 
 func (m *auctionMockPermissions) AMPException() bool {
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index bb766aa9..824e32f1 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -254,8 +254,8 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi
 	return ok, nil
 }
 
-func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
-	return true, nil
+func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
+	return true, true, nil
 }
 
 func (g *gdprPerms) AMPException() bool {
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 9dc81eb1..289db3f4 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -122,9 +122,8 @@ func TestAMPPageInfo(t *testing.T) {
 }
 
 func TestGDPRConsent(t *testing.T) {
-	consent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
+	consent := "BOu5On0Ou5On0ADACHENAO7pqzAAppY"
 	existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
-
 	digitrust := &openrtb_ext.ExtUserDigiTrust{
 		ID:   "anyDigitrustID",
 		KeyV: 1,
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index fdd6b3a4..c3b9267b 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -915,7 +915,7 @@ func TestCurrencyTrunc(t *testing.T) {
 	assert.ElementsMatch(t, errL, []error{&expectedError})
 }
 
-func TestCCPAInvalidValueWarning(t *testing.T) {
+func TestCCPAInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		&nobidExchange{},
 		newParamsValidator(t),
@@ -943,21 +943,23 @@ func TestCCPAInvalidValueWarning(t *testing.T) {
 					W: &ui,
 					H: &ui,
 				},
-				Ext: json.RawMessage("{\"appnexus\": {\"placementId\": 5667}}"),
+				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
 			},
 		},
 		Site: &openrtb.Site{
 			ID: "myID",
 		},
 		Regs: &openrtb.Regs{
-			Ext: json.RawMessage("{\"us_privacy\":\"invalid by length\"}"),
+			Ext: json.RawMessage(`{"us_privacy":"invalid by length"}`),
 		},
 	}
 
 	errL := deps.validateRequest(&req)
 
-	expectedError := errortypes.Warning{Message: "CCPA value is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"}
-	assert.ElementsMatch(t, errL, []error{&expectedError})
+	expectedWarning := errortypes.InvalidPrivacyConsent{Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"}
+	assert.ElementsMatch(t, errL, []error{&expectedWarning})
+
+	assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request")
 }
 
 // nobidExchange is a well-behaved exchange which always bids "no bid".
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 5ba34068..631cb277 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1,6 +1,7 @@
 package openrtb2
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
@@ -860,12 +861,12 @@ func TestParseVideoRequestWithUserAgentAndHeader(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Failed to fetch a valid request: %v", err)
 	}
-
 	headers := http.Header{}
 	headers.Add("User-Agent", "TestHeader")
 
 	deps := mockDeps(t, ex)
-	req, valErr, podErr := deps.parseVideoRequest(reqData, headers)
+	reqBody := string(getRequestPayload(t, reqData))
+	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
 
 	assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request")
 	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
@@ -883,7 +884,8 @@ func TestParseVideoRequestWithUserAgentAndEmptyHeader(t *testing.T) {
 	headers := http.Header{}
 
 	deps := mockDeps(t, ex)
-	req, valErr, podErr := deps.parseVideoRequest(reqData, headers)
+	reqBody := string(getRequestPayload(t, reqData))
+	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
 
 	assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request")
 	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
@@ -902,7 +904,8 @@ func TestParseVideoRequestWithoutUserAgentWithHeader(t *testing.T) {
 	headers.Add("User-Agent", "TestHeader")
 
 	deps := mockDeps(t, ex)
-	req, valErr, podErr := deps.parseVideoRequest(reqData, headers)
+	reqBody := string(getRequestPayload(t, reqData))
+	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
 
 	assert.Equal(t, "TestHeader", req.Device.UA, "Device.ua should be taken from request header")
 	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
@@ -920,7 +923,8 @@ func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) {
 	headers := http.Header{}
 
 	deps := mockDeps(t, ex)
-	req, valErr, podErr := deps.parseVideoRequest(reqData, headers)
+	reqBody := string(getRequestPayload(t, reqData))
+	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
 
 	assert.Equal(t, "", req.Device.UA, "Device.ua should be empty")
 	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
@@ -942,7 +946,8 @@ func TestParseVideoRequestWithEncodedUserAgentInHeader(t *testing.T) {
 	headers.Add("User-Agent", uaEncoded)
 
 	deps := mockDeps(t, ex)
-	req, valErr, podErr := deps.parseVideoRequest(reqData, headers)
+	reqBody := string(getRequestPayload(t, reqData))
+	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
 
 	assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header")
 	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
@@ -963,7 +968,8 @@ func TestParseVideoRequestWithDecodedUserAgentInHeader(t *testing.T) {
 	headers.Add("User-Agent", uaDecoded)
 
 	deps := mockDeps(t, ex)
-	req, valErr, podErr := deps.parseVideoRequest(reqData, headers)
+	reqBody := string(getRequestPayload(t, reqData))
+	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
 
 	assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header")
 	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
@@ -1036,6 +1042,71 @@ func TestCreateImpressionTemplate(t *testing.T) {
 	assert.Equal(t, res.Video.PlaybackMethod, []openrtb.PlaybackMethod{7, 8}, "Incorrect video playback method")
 }
 
+func TestCCPA(t *testing.T) {
+	testCases := []struct {
+		description         string
+		testFilePath        string
+		expectConsentString bool
+	}{
+		{
+			description:         "Missing Consent",
+			testFilePath:        "sample-requests/video/video_valid_sample.json",
+			expectConsentString: false,
+		},
+		{
+			description:         "Valid Consent",
+			testFilePath:        "sample-requests/video/video_valid_sample_ccpa_valid.json",
+			expectConsentString: true,
+		},
+		{
+			description:         "Malformed Consent",
+			testFilePath:        "sample-requests/video/video_valid_sample_ccpa_malformed.json",
+			expectConsentString: false,
+		},
+	}
+
+	for _, test := range testCases {
+		// Load Test Request
+		requestContainerBytes, err := ioutil.ReadFile(test.testFilePath)
+		if err != nil {
+			t.Fatalf("%s: Failed to fetch a valid request: %v", test.description, err)
+		}
+		requestBytes := getRequestPayload(t, requestContainerBytes)
+
+		// Create HTTP Request + Response Recorder
+		httpRequest := httptest.NewRequest("POST", "/openrtb2/video", bytes.NewReader(requestBytes))
+		httpResponseRecorder := httptest.NewRecorder()
+
+		// Run Test
+		ex := &mockExchangeVideo{}
+		mockDeps(t, ex).VideoAuctionEndpoint(httpResponseRecorder, httpRequest, nil)
+
+		// Validate Request To Exchange
+		// - An error should never be generated for CCPA problems.
+		if ex.lastRequest == nil {
+			t.Fatalf("%s: The request never made it into the exchange.", test.description)
+		}
+		extRegs := &openrtb_ext.ExtRegs{}
+		if err = json.Unmarshal(ex.lastRequest.Regs.Ext, extRegs); err != nil {
+			t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err)
+		}
+		if test.expectConsentString {
+			assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent")
+		} else {
+			assert.Empty(t, extRegs.USPrivacy, test.description+":consent")
+		}
+
+		// Validate HTTP Response
+		responseBytes := httpResponseRecorder.Body.Bytes()
+		response := &openrtb_ext.BidResponseVideo{}
+		if err := json.Unmarshal(responseBytes, response); err != nil {
+			t.Fatalf("%s: Unable to unmarshal response.", test.description)
+		}
+		assert.Len(t, ex.lastRequest.Imp, 11, test.description+":imps")
+		assert.Len(t, response.AdPods, 5, test.description+":adpods")
+	}
+}
+
 func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *pbsmetrics.Metrics, *mockAnalyticsModule) {
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
 	mockModule := &mockAnalyticsModule{}
@@ -1166,3 +1237,19 @@ var testVideoStoredImpData = map[string]json.RawMessage{
 var testVideoStoredRequestData = map[string]json.RawMessage{
 	"80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`),
 }
+
+func loadValidRequest(t *testing.T) *openrtb_ext.BidRequestVideo {
+	reqData, err := ioutil.ReadFile("sample-requests/video/video_valid_sample.json")
+	if err != nil {
+		t.Fatalf("Failed to fetch a valid request: %v", err)
+	}
+
+	reqBody := getRequestPayload(t, reqData)
+
+	reqVideo := &openrtb_ext.BidRequestVideo{}
+	if err := json.Unmarshal(reqBody, reqVideo); err != nil {
+		t.Fatalf("Failed to unmarshal the request: %v", err)
+	}
+
+	return reqVideo
+}
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index 8499ac1c..3f47b257 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -437,8 +437,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return false, nil
 }
 
-func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
-	return g.allowPI, nil
+func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
+	return g.allowPI, g.allowPI, nil
 }
 
 func (g *mockPermsSetUID) AMPException() bool {
diff --git a/exchange/adapter_map_test.go b/exchange/adapter_map_test.go
index a732f357..f472ab1d 100644
--- a/exchange/adapter_map_test.go
+++ b/exchange/adapter_map_test.go
@@ -7,11 +7,12 @@ import (
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config"
 )
 
 func TestNewAdapterMap(t *testing.T) {
 	cfg := &config.Configuration{Adapters: blankAdapterConfig(openrtb_ext.BidderList())}
-	adapterMap := newAdapterMap(nil, cfg, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()))
+	adapterMap := newAdapterMap(nil, cfg, adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), &metricsConfig.DummyMetricsEngine{})
 	for _, bidderName := range openrtb_ext.BidderMap {
 		if bidder, ok := adapterMap[bidderName]; bidder == nil || !ok {
 			t.Errorf("adapterMap missing expected Bidder: %s", string(bidderName))
@@ -38,7 +39,7 @@ func TestNewAdapterMapDisabledAdapters(t *testing.T) {
 			}
 		}
 	}
-	adapterMap := newAdapterMap(nil, &config.Configuration{Adapters: cfgAdapters}, adapters.ParseBidderInfos(cfgAdapters, "../static/bidder-info", bidderList))
+	adapterMap := newAdapterMap(nil, &config.Configuration{Adapters: cfgAdapters}, adapters.ParseBidderInfos(cfgAdapters, "../static/bidder-info", bidderList), &metricsConfig.DummyMetricsEngine{})
 	for _, bidderName := range openrtb_ext.BidderMap {
 		if bidder, ok := adapterMap[bidderName]; bidder == nil || !ok {
 			if inList(bidderList, bidderName) {
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index f20b431c..fa04e6a4 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -12,8 +12,10 @@ import (
 
 	"github.com/mxmCherry/openrtb"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currencies"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config"
 	"github.com/stretchr/testify/assert"
 
 	nativeRequests "github.com/mxmCherry/openrtb/native/request"
@@ -64,7 +66,7 @@ func TestSingleBidder(t *testing.T) {
 		},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client())
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 	currencyConverter := currencies.NewRateConverterDefault()
 	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
@@ -152,7 +154,7 @@ func TestMultiBidder(t *testing.T) {
 			}},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client())
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 	currencyConverter := currencies.NewRateConverterDefault()
 	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
@@ -510,7 +512,7 @@ func TestMultiCurrencies(t *testing.T) {
 		)
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client())
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 		currencyConverter := currencies.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -658,7 +660,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client())
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 		currencyConverter := currencies.NewRateConverterDefault()
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
@@ -824,7 +826,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client())
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 		currencyConverter := currencies.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -939,7 +941,7 @@ func TestServerCallDebugging(t *testing.T) {
 			Headers: http.Header{},
 		},
 	}
-	bidder := adaptBidder(bidderImpl, server.Client())
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 	currencyConverter := currencies.NewRateConverterDefault()
 
 	bids, _ := bidder.requestBid(
@@ -1051,7 +1053,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			bidResponse: tc.mockBidderResponse,
 		}
-		bidder := adaptBidder(bidderImpl, server.Client())
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 		currencyConverter := currencies.NewRateConverterDefault()
 
 		seatBids, _ := bidder.requestBid(
@@ -1072,7 +1074,7 @@ func TestMobileNativeTypes(t *testing.T) {
 }
 
 func TestErrorReporting(t *testing.T) {
-	bidder := adaptBidder(&bidRejector{}, nil)
+	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 	currencyConverter := currencies.NewRateConverterDefault()
 	bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 	if bids != nil {
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index f8630968..72de1d42 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -8,12 +8,14 @@ import (
 	"testing"
 	"time"
 
+	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currencies"
 
 	"github.com/prebid/prebid-server/gdpr"
 
 	"github.com/prebid/prebid-server/pbsmetrics"
 	metricsConf "github.com/prebid/prebid-server/pbsmetrics/config"
+	metricsConfig "github.com/prebid/prebid-server/pbsmetrics/config"
 
 	"github.com/mxmCherry/openrtb"
 	"github.com/prebid/prebid-server/adapters"
@@ -132,7 +134,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerU
 		adapterMap[bidder] = adaptBidder(&mockTargetingBidder{
 			mockServerURL: mockServerURL,
 			bids:          bids,
-		}, client)
+		}, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{})
 	}
 	return adapterMap
 }
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 53d6b85c..acbf25ff 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -24,11 +24,11 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return true, nil
 }
 
-func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
+func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
 	if bidder == "appnexus" {
-		return true, nil
+		return true, true, nil
 	}
-	return false, nil
+	return false, false, nil
 }
 
 func (p *permissionsMock) AMPException() bool {
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index 8b89577d..f05f25e8 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -10,6 +10,9 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 
 	"github.com/prebid/go-gdpr/vendorlist"
+	"github.com/prebid/go-gdpr/vendorlist2"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestNoConsentButAllowByDefault(t *testing.T) {
@@ -55,10 +58,10 @@ func TestNoConsentAndRejectByDefault(t *testing.T) {
 func TestAllowedSyncs(t *testing.T) {
 	vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{
 		2: {
-			purposes: []uint8{1},
+			purposes: []int{1},
 		},
 		3: {
-			purposes: []uint8{1},
+			purposes: []int{1},
 		},
 	})
 	perms := permissionsImpl{
@@ -91,10 +94,10 @@ func TestAllowedSyncs(t *testing.T) {
 func TestProhibitedPurposes(t *testing.T) {
 	vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{
 		2: {
-			purposes: []uint8{1}, // cookie reads/writes
+			purposes: []int{1}, // cookie reads/writes
 		},
 		3: {
-			purposes: []uint8{3}, // ad personalization
+			purposes: []int{3}, // ad personalization
 		},
 	})
 	perms := permissionsImpl{
@@ -127,10 +130,10 @@ func TestProhibitedPurposes(t *testing.T) {
 func TestProhibitedVendors(t *testing.T) {
 	vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{
 		2: {
-			purposes: []uint8{1}, // cookie reads/writes
+			purposes: []int{1}, // cookie reads/writes
 		},
 		3: {
-			purposes: []uint8{3}, // ad personalization
+			purposes: []int{3}, // ad personalization
 		},
 	})
 	perms := permissionsImpl{
@@ -179,10 +182,10 @@ func TestMalformedConsent(t *testing.T) {
 func TestAllowPersonalInfo(t *testing.T) {
 	vendorListData := mockVendorListData(t, 1, map[uint16]*purposes{
 		2: {
-			purposes: []uint8{1}, // cookie reads/writes
+			purposes: []int{1}, // cookie reads/writes
 		},
 		3: {
-			purposes: []uint8{1, 3}, // ad personalization
+			purposes: []int{1, 3}, // ad personalization
 		},
 	})
 	perms := permissionsImpl{
@@ -204,21 +207,377 @@ func TestAllowPersonalInfo(t *testing.T) {
 	}
 
 	// PI needs both purposes to succeed
-	allowPI, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
+	allowPI, _, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, false, allowPI)
 
-	allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
+	allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, true, allowPI)
 
 	// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
 	perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1}
-	allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
+	allowPI, _, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, true, allowPI)
 }
 
+var tcf2BasicPurposes = map[uint16]*purposes{
+	2:  {purposes: []int{1}},       //cookie reads/writes
+	6:  {purposes: []int{1, 2, 4}}, // ad personalization
+	8:  {purposes: []int{1, 7}},
+	10: {purposes: []int{2, 4, 7}},
+	32: {purposes: []int{1, 2, 4, 7}},
+}
+var tcf2LegitInterests = map[uint16]*purposes{
+	6: {purposes: []int{7}},
+	8: {purposes: []int{2, 4}},
+}
+var tcf2SpecialPuproses = map[uint16]*purposes{
+	6:  {purposes: []int{1}},
+	10: {purposes: []int{1}},
+}
+var tcf2FlexPurposes = map[uint16]*purposes{
+	6: {purposes: []int{1, 2, 4, 7}},
+}
+var tcf2Config = config.GDPR{
+	HostVendorID: 2,
+	TCF2: config.TCF2{
+		Enabled:         true,
+		Purpose1:        config.PurposeDetail{Enabled: true},
+		Purpose2:        config.PurposeDetail{Enabled: true},
+		Purpose7:        config.PurposeDetail{Enabled: true},
+		SpecialPurpose1: config.PurposeDetail{Enabled: true},
+	},
+}
+
+type tcf2TestDef struct {
+	description string
+	bidder      openrtb_ext.BidderName
+	consent     string
+	allowPI     bool
+	allowGeo    bool
+}
+
+func TestAllowPersonalInfoTCF2(t *testing.T) {
+	vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
+	// PI needs all purposes to succeed
+	testDefs := []tcf2TestDef{
+		{
+			description: "Appnexus vendor test, insufficient purposes claimed",
+			bidder:      openrtb_ext.BidderAppnexus,
+			consent:     "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+		{
+			description: "Pubmatic vendor test, flex purposes claimed",
+			bidder:      openrtb_ext.BidderPubmatic,
+			consent:     "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+			allowPI:     true,
+			allowGeo:    true,
+		},
+		{
+			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
+			bidder:      openrtb_ext.BidderRubicon,
+			consent:     "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
+			allowPI:     true,
+			allowGeo:    false,
+		},
+	}
+
+	for _, td := range testDefs {
+		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
+		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+	}
+}
+
+func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) {
+	vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+	// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
+	perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1}
+	allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed")
+	assert.EqualValuesf(t, true, allowPI, "AllowPI failure")
+	assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure")
+
+}
+
+func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) {
+	vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 32,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				15: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+
+	// COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only,
+	// Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent
+	testDefs := []tcf2TestDef{
+		{
+			description: "Appnexus vendor test, insufficient purposes claimed",
+			bidder:      openrtb_ext.BidderAppnexus,
+			consent:     "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+		{
+			description: "Pubmatic vendor test, flex purposes claimed",
+			bidder:      openrtb_ext.BidderPubmatic,
+			consent:     "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+		{
+			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
+			bidder:      openrtb_ext.BidderRubicon,
+			consent:     "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+	}
+
+	for _, td := range testDefs {
+		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
+		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+	}
+}
+
+func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) {
+	vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 10,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+	perms.cfg.TCF2.PurposeOneTreatment.Enabled = true
+	perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = true
+
+	// COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set
+	testDefs := []tcf2TestDef{
+		{
+			description: "Appnexus vendor test, insufficient purposes claimed",
+			bidder:      openrtb_ext.BidderAppnexus,
+			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+		{
+			description: "Pubmatic vendor test, flex purposes claimed",
+			bidder:      openrtb_ext.BidderPubmatic,
+			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
+			allowPI:     true,
+			allowGeo:    true,
+		},
+		{
+			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
+			bidder:      openrtb_ext.BidderRubicon,
+			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
+			allowPI:     true,
+			allowGeo:    false,
+		},
+	}
+
+	for _, td := range testDefs {
+		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
+		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+	}
+}
+
+func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) {
+	vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 10,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+	perms.cfg.TCF2.PurposeOneTreatment.Enabled = true
+	perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false
+
+	// COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set
+	testDefs := []tcf2TestDef{
+		{
+			description: "Appnexus vendor test, insufficient purposes claimed",
+			bidder:      openrtb_ext.BidderAppnexus,
+			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+		{
+			description: "Pubmatic vendor test, flex purposes claimed",
+			bidder:      openrtb_ext.BidderPubmatic,
+			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
+			allowPI:     false,
+			allowGeo:    true,
+		},
+		{
+			description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed",
+			bidder:      openrtb_ext.BidderRubicon,
+			consent:     "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA",
+			allowPI:     false,
+			allowGeo:    false,
+		},
+	}
+
+	for _, td := range testDefs {
+		allowPI, allowGeo, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", td.consent)
+		assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description)
+		assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description)
+		assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description)
+	}
+}
+
+func TestAllowSyncTCF2(t *testing.T) {
+	vendorListData := mockVendorListDataTCF2(t, 2, tcf2BasicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
+	assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure")
+
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing BidderSyncAllowed")
+	assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure")
+}
+
+func TestProhibitedPurposeSyncTCF2(t *testing.T) {
+	basicPurposes := tcf2BasicPurposes
+	basicPurposes[8] = &purposes{purposes: []int{7}}
+	vendorListData := mockVendorListDataTCF2(t, 2, basicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+	perms.cfg.HostVendorID = 8
+
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
+	assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure")
+
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing BidderSyncAllowed")
+	assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure")
+}
+
+func TestProhibitedVendorSyncTCF2(t *testing.T) {
+	basicPurposes := tcf2BasicPurposes
+	basicPurposes[10] = &purposes{purposes: []int{1}}
+	vendorListData := mockVendorListDataTCF2(t, 2, basicPurposes, tcf2LegitInterests, tcf2FlexPurposes, tcf2SpecialPuproses)
+	perms := permissionsImpl{
+		cfg: tcf2Config,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+			openrtb_ext.BidderOpenx:    10,
+		},
+		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
+			tCF1: nil,
+			tCF2: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
+		},
+	}
+	perms.cfg.HostVendorID = 10
+
+	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 4, 6
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
+	assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure")
+
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	assert.NoErrorf(t, err, "Error processing BidderSyncAllowed")
+	assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure")
+}
+
 func parseVendorListData(t *testing.T, data string) vendorlist.VendorList {
 	t.Helper()
 	parsed, err := vendorlist.ParseEagerly([]byte(data))
@@ -228,6 +587,15 @@ func parseVendorListData(t *testing.T, data string) vendorlist.VendorList {
 	return parsed
 }
 
+func parseVendorListDataV2(t *testing.T, data string) vendorlist.VendorList {
+	t.Helper()
+	parsed, err := vendorlist2.ParseEagerly([]byte(data))
+	if err != nil {
+		t.Fatalf("Failed to parse vendor list data. %v", err)
+	}
+	return parsed
+}
+
 func listFetcher(lists map[uint16]vendorlist.VendorList) func(context.Context, uint16) (vendorlist.VendorList, error) {
 	return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
 		data, ok := lists[id]
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index 8197fa26..824f9178 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -15,12 +15,12 @@ import (
 func TestVendorFetch(t *testing.T) {
 	vendorListOne := mockVendorListData(t, 1, map[uint16]*purposes{
 		32: {
-			purposes: []uint8{1, 2},
+			purposes: []int{1, 2},
 		},
 	})
 	vendorListTwo := mockVendorListData(t, 2, map[uint16]*purposes{
 		32: {
-			purposes: []uint8{1, 2, 3},
+			purposes: []int{1, 2, 3},
 		},
 	})
 	server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{
@@ -47,12 +47,12 @@ func TestVendorFetch(t *testing.T) {
 func TestLazyFetch(t *testing.T) {
 	firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
 		32: {
-			purposes: []uint8{1, 2},
+			purposes: []int{1, 2},
 		},
 	})
 	secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
 		3: {
-			purposes: []uint8{1},
+			purposes: []int{1},
 		},
 	})
 	server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{
@@ -73,7 +73,7 @@ func TestLazyFetch(t *testing.T) {
 func TestInitialTimeout(t *testing.T) {
 	list := mockVendorListData(t, 1, map[uint16]*purposes{
 		32: {
-			purposes: []uint8{1, 2},
+			purposes: []int{1, 2},
 		},
 	})
 	server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{
@@ -91,12 +91,12 @@ func TestInitialTimeout(t *testing.T) {
 func TestFetchThrottling(t *testing.T) {
 	vendorListTwo := mockVendorListData(t, 2, map[uint16]*purposes{
 		32: {
-			purposes: []uint8{1, 2},
+			purposes: []int{1, 2},
 		},
 	})
 	vendorListThree := mockVendorListData(t, 3, map[uint16]*purposes{
 		32: {
-			purposes: []uint8{1, 2},
+			purposes: []int{1, 2},
 		},
 	})
 	server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{
@@ -174,8 +174,8 @@ func mockServer(latestVersion int, responses map[int]string) func(http.ResponseW
 
 func mockVendorListData(t *testing.T, version uint16, vendors map[uint16]*purposes) string {
 	type vendorContract struct {
-		ID       uint16  `json:"id"`
-		Purposes []uint8 `json:"purposeIds"`
+		ID       uint16 `json:"id"`
+		Purposes []int  `json:"purposeIds"`
 	}
 
 	type vendorListContract struct {
@@ -203,6 +203,72 @@ func mockVendorListData(t *testing.T, version uint16, vendors map[uint16]*purpos
 	return string(data)
 }
 
+type purposeMap map[uint16]*purposes
+
+func mockVendorListDataTCF2(t *testing.T, version uint16, basicPurposes purposeMap, legitInterests purposeMap, flexPurposes purposeMap, specialPurposes purposeMap) string {
+	type vendorContract struct {
+		ID               uint16 `json:"id"`
+		Purposes         []int  `json:"purposes"`
+		LegIntPurposes   []int  `json:"legIntPurposes"`
+		FlexiblePurposes []int  `json:"flexiblePurposes"`
+		SpecialPurposes  []int  `json:"specialPurposes"`
+	}
+
+	type vendorListContract struct {
+		Version uint16                    `json:"vendorListVersion"`
+		Vendors map[string]vendorContract `json:"vendors"`
+	}
+
+	vendors := make(map[string]vendorContract, len(basicPurposes))
+	for id, purpose := range basicPurposes {
+		sid := strconv.Itoa(int(id))
+		vendor, ok := vendors[sid]
+		if !ok {
+			vendor = vendorContract{ID: id}
+		}
+		vendor.Purposes = purpose.purposes
+		vendors[sid] = vendor
+	}
+
+	for id, purpose := range legitInterests {
+		sid := strconv.Itoa(int(id))
+		vendor, ok := vendors[sid]
+		if !ok {
+			vendor = vendorContract{ID: id}
+		}
+		vendor.LegIntPurposes = purpose.purposes
+		vendors[sid] = vendor
+	}
+
+	for id, purpose := range flexPurposes {
+		sid := strconv.Itoa(int(id))
+		vendor, ok := vendors[sid]
+		if !ok {
+			vendor = vendorContract{ID: id}
+		}
+		vendor.FlexiblePurposes = purpose.purposes
+		vendors[sid] = vendor
+	}
+
+	for id, purpose := range specialPurposes {
+		sid := strconv.Itoa(int(id))
+		vendor, ok := vendors[sid]
+		if !ok {
+			vendor = vendorContract{ID: id}
+		}
+		vendor.SpecialPurposes = purpose.purposes
+		vendors[sid] = vendor
+	}
+
+	obj := vendorListContract{
+		Version: version,
+		Vendors: vendors,
+	}
+	data, err := json.Marshal(obj)
+	assertNilErr(t, err)
+	return string(data)
+}
+
 func testURLMaker(server *httptest.Server) func(uint16, uint8) string {
 	url := server.URL
 	return func(version uint16, TCFVer uint8) string {
@@ -220,5 +286,5 @@ func testConfig() config.GDPR {
 }
 
 type purposes struct {
-	purposes []uint8
+	purposes []int
 }
diff --git a/pbsmetrics/go_metrics_test.go b/pbsmetrics/go_metrics_test.go
index 253ff69e..25f75e77 100644
--- a/pbsmetrics/go_metrics_test.go
+++ b/pbsmetrics/go_metrics_test.go
@@ -53,6 +53,9 @@ func TestNewMetrics(t *testing.T) {
 
 	ensureContains(t, registry, "queued_requests.video.rejected", m.RequestsQueueTimer[ReqTypeVideo][false])
 	ensureContains(t, registry, "queued_requests.video.accepted", m.RequestsQueueTimer[ReqTypeVideo][true])
+
+	ensureContains(t, registry, "timeout_notification.ok", m.TimeoutNotificationSuccess)
+	ensureContains(t, registry, "timeout_notification.failed", m.TimeoutNotificationFailure)
 }
 
 func TestRecordBidType(t *testing.T) {
diff --git a/pbsmetrics/prometheus/prometheus_test.go b/pbsmetrics/prometheus/prometheus_test.go
index e4d6a4f7..24c50492 100644
--- a/pbsmetrics/prometheus/prometheus_test.go
+++ b/pbsmetrics/prometheus/prometheus_test.go
@@ -923,6 +923,27 @@ func TestRecordRequestQueueTimeMetric(t *testing.T) {
 	}
 }
 
+func TestTimeoutNotifications(t *testing.T) {
+	m := createMetricsForTesting()
+
+	m.RecordTimeoutNotice(true)
+	m.RecordTimeoutNotice(true)
+	m.RecordTimeoutNotice(false)
+
+	assertCounterVecValue(t, "", "timeout_notifications:ok", m.timeout_notifications,
+		float64(2),
+		prometheus.Labels{
+			successLabel: requestSuccessful,
+		})
+
+	assertCounterVecValue(t, "", "timeout_notifications:fail", m.timeout_notifications,
+		float64(1),
+		prometheus.Labels{
+			successLabel: requestFailed,
+		})
+
+}
+
 func assertCounterValue(t *testing.T, description, name string, counter prometheus.Counter, expected float64) {
 	m := dto.Metric{}
 	counter.Write(&m)
diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go
index 740f95a8..e9b4c452 100644
--- a/privacy/ccpa/policy_test.go
+++ b/privacy/ccpa/policy_test.go
@@ -112,6 +112,48 @@ func TestWrite(t *testing.T) {
 			request:     &openrtb.BidRequest{},
 			expected:    &openrtb.BidRequest{},
 		},
+		{
+			description: "Disabled - Nil Request",
+			policy:      Policy{Value: ""},
+			request:     nil,
+			expected:    nil,
+		},
+		{
+			description: "Disabled - Empty Regs.Ext",
+			policy:      Policy{Value: ""},
+			request:     &openrtb.BidRequest{Regs: &openrtb.Regs{}},
+			expected:    &openrtb.BidRequest{Regs: &openrtb.Regs{}},
+		},
+		{
+			description: "Disabled - Remove From Request",
+			policy:      Policy{Value: ""},
+			request: &openrtb.BidRequest{Regs: &openrtb.Regs{
+				Ext: json.RawMessage(`{"us_privacy":"toBeRemoved"}`)}},
+			expected: &openrtb.BidRequest{Regs: &openrtb.Regs{}},
+		},
+		{
+			description: "Disabled - Remove From Request, Leave Other req Values",
+			policy:      Policy{Value: ""},
+			request: &openrtb.BidRequest{Regs: &openrtb.Regs{
+				COPPA: 42,
+				Ext:   json.RawMessage(`{"us_privacy":"toBeRemoved"}`)}},
+			expected: &openrtb.BidRequest{Regs: &openrtb.Regs{
+				COPPA: 42}},
+		},
+		{
+			description: "Disabled - Remove From Request, Leave Other req.ext Values",
+			policy:      Policy{Value: ""},
+			request: &openrtb.BidRequest{Regs: &openrtb.Regs{
+				Ext: json.RawMessage(`{"existing":"any","us_privacy":"toBeRemoved"}`)}},
+			expected: &openrtb.BidRequest{Regs: &openrtb.Regs{
+				Ext: json.RawMessage(`{"existing":"any"}`)}},
+		},
+		{
+			description: "Enabled - Nil Request",
+			policy:      Policy{Value: "anyValue"},
+			request:     nil,
+			expected:    nil,
+		},
 		{
 			description: "Enabled With Nil Request Regs Object",
 			policy:      Policy{Value: "anyValue"},
diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go
index 25e08b5e..0e82648d 100644
--- a/privacy/enforcement_test.go
+++ b/privacy/enforcement_test.go
@@ -17,27 +17,40 @@ func TestAny(t *testing.T) {
 		{
 			description: "All False",
 			enforcement: Enforcement{
-				CCPA:  false,
-				COPPA: false,
-				GDPR:  false,
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    false,
+				GDPRGeo: false,
 			},
 			expected: false,
 		},
 		{
 			description: "All True",
 			enforcement: Enforcement{
-				CCPA:  true,
-				COPPA: true,
-				GDPR:  true,
+				CCPA:    true,
+				COPPA:   true,
+				GDPR:    true,
+				GDPRGeo: true,
 			},
 			expected: true,
 		},
 		{
 			description: "Mixed",
 			enforcement: Enforcement{
-				CCPA:  false,
-				COPPA: true,
-				GDPR:  false,
+				CCPA:    false,
+				COPPA:   true,
+				GDPR:    false,
+				GDPRGeo: false,
+			},
+			expected: true,
+		},
+		{
+			description: "GDPRGeo only",
+			enforcement: Enforcement{
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    false,
+				GDPRGeo: true,
 			},
 			expected: true,
 		},
@@ -62,9 +75,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "All Enforced",
 			enforcement: Enforcement{
-				CCPA:  true,
-				COPPA: true,
-				GDPR:  true,
+				CCPA:    true,
+				COPPA:   true,
+				GDPR:    true,
+				GDPRGeo: true,
 			},
 			ampGDPRException:   false,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
@@ -75,9 +89,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "CCPA Only",
 			enforcement: Enforcement{
-				CCPA:  true,
-				COPPA: false,
-				GDPR:  false,
+				CCPA:    true,
+				COPPA:   false,
+				GDPR:    false,
+				GDPRGeo: false,
 			},
 			ampGDPRException:   false,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
@@ -88,9 +103,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "COPPA Only",
 			enforcement: Enforcement{
-				CCPA:  false,
-				COPPA: true,
-				GDPR:  false,
+				CCPA:    false,
+				COPPA:   true,
+				GDPR:    false,
+				GDPRGeo: false,
 			},
 			ampGDPRException:   false,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
@@ -101,9 +117,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "GDPR Only",
 			enforcement: Enforcement{
-				CCPA:  false,
-				COPPA: false,
-				GDPR:  true,
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    true,
+				GDPRGeo: true,
 			},
 			ampGDPRException:   false,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
@@ -114,9 +131,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "GDPR Only, ampGDPRException",
 			enforcement: Enforcement{
-				CCPA:  false,
-				COPPA: false,
-				GDPR:  true,
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    true,
+				GDPRGeo: true,
 			},
 			ampGDPRException:   true,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
@@ -127,9 +145,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "CCPA Only, ampGDPRException",
 			enforcement: Enforcement{
-				CCPA:  true,
-				COPPA: false,
-				GDPR:  false,
+				CCPA:    true,
+				COPPA:   false,
+				GDPR:    false,
+				GDPRGeo: false,
 			},
 			ampGDPRException:   true,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
@@ -140,9 +159,10 @@ func TestApply(t *testing.T) {
 		{
 			description: "COPPA and GDPR, ampGDPRException",
 			enforcement: Enforcement{
-				CCPA:  false,
-				COPPA: true,
-				GDPR:  true,
+				CCPA:    false,
+				COPPA:   true,
+				GDPR:    true,
+				GDPRGeo: true,
 			},
 			ampGDPRException:   true,
 			expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
@@ -150,6 +170,34 @@ func TestApply(t *testing.T) {
 			expectedUser:       ScrubStrategyUserIDAndDemographic,
 			expectedUserGeo:    ScrubStrategyGeoFull,
 		},
+		{
+			description: "GDPR Only, no Geo",
+			enforcement: Enforcement{
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    true,
+				GDPRGeo: false,
+			},
+			ampGDPRException:   false,
+			expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
+			expectedDeviceGeo:  ScrubStrategyGeoNone,
+			expectedUser:       ScrubStrategyUserID,
+			expectedUserGeo:    ScrubStrategyGeoNone,
+		},
+		{
+			description: "GDPR Only, Geo only",
+			enforcement: Enforcement{
+				CCPA:    false,
+				COPPA:   false,
+				GDPR:    false,
+				GDPRGeo: true,
+			},
+			ampGDPRException:   false,
+			expectedDeviceIPv6: ScrubStrategyIPV6None,
+			expectedDeviceGeo:  ScrubStrategyGeoReducedPrecision,
+			expectedUser:       ScrubStrategyUserNone,
+			expectedUserGeo:    ScrubStrategyGeoReducedPrecision,
+		},
 	}
 
 	for _, test := range testCases {
diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go
index a6c4bde3..67241019 100644
--- a/privacy/scrubber_test.go
+++ b/privacy/scrubber_test.go
@@ -1,6 +1,7 @@
 package privacy
 
 import (
+	"encoding/json"
 	"testing"
 
 	"github.com/mxmCherry/openrtb"
@@ -241,6 +242,7 @@ func TestScrubUser(t *testing.T) {
 		BuyerUID: "anyBuyerUID",
 		Yob:      42,
 		Gender:   "anyGender",
+		Ext:      json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
 		Geo: &openrtb.Geo{
 			Lat:   123.456,
 			Lon:   678.89,
@@ -251,30 +253,32 @@ func TestScrubUser(t *testing.T) {
 	}
 
 	testCases := []struct {
-		expected    *openrtb.User
-		user        ScrubStrategyUser
-		geo         ScrubStrategyGeo
 		description string
+		expected    *openrtb.User
+		scrubUser   ScrubStrategyUser
+		scrubGeo    ScrubStrategyGeo
 	}{
 		{
-			description: "Demographic Age And Gender & Geo Full",
+			description: "User ID And Demographic & Geo Full",
 			expected: &openrtb.User{
 				ID:       "",
 				BuyerUID: "",
 				Yob:      0,
 				Gender:   "",
+				Ext:      json.RawMessage(`{}`),
 				Geo:      &openrtb.Geo{},
 			},
-			user: ScrubStrategyUserIDAndDemographic,
-			geo:  ScrubStrategyGeoFull,
+			scrubUser: ScrubStrategyUserIDAndDemographic,
+			scrubGeo:  ScrubStrategyGeoFull,
 		},
 		{
-			description: "Demographic Age And Gender & Geo Reduced",
+			description: "User ID And Demographic & Geo Reduced",
 			expected: &openrtb.User{
 				ID:       "",
 				BuyerUID: "",
 				Yob:      0,
 				Gender:   "",
+				Ext:      json.RawMessage(`{}`),
 				Geo: &openrtb.Geo{
 					Lat:   123.46,
 					Lon:   678.89,
@@ -283,16 +287,17 @@ func TestScrubUser(t *testing.T) {
 					ZIP:   "some zip",
 				},
 			},
-			user: ScrubStrategyUserIDAndDemographic,
-			geo:  ScrubStrategyGeoReducedPrecision,
+			scrubUser: ScrubStrategyUserIDAndDemographic,
+			scrubGeo:  ScrubStrategyGeoReducedPrecision,
 		},
 		{
-			description: "Demographic Full & Geo None",
+			description: "User ID And Demographic & Geo None",
 			expected: &openrtb.User{
 				ID:       "",
 				BuyerUID: "",
 				Yob:      0,
 				Gender:   "",
+				Ext:      json.RawMessage(`{}`),
 				Geo: &openrtb.Geo{
 					Lat:   123.456,
 					Lon:   678.89,
@@ -301,28 +306,30 @@ func TestScrubUser(t *testing.T) {
 					ZIP:   "some zip",
 				},
 			},
-			user: ScrubStrategyUserIDAndDemographic,
-			geo:  ScrubStrategyGeoNone,
+			scrubUser: ScrubStrategyUserIDAndDemographic,
+			scrubGeo:  ScrubStrategyGeoNone,
 		},
 		{
-			description: "Demographic Buyer ID & Geo Full",
+			description: "User ID & Geo Full",
 			expected: &openrtb.User{
 				ID:       "",
 				BuyerUID: "",
 				Yob:      42,
 				Gender:   "anyGender",
+				Ext:      json.RawMessage(`{}`),
 				Geo:      &openrtb.Geo{},
 			},
-			user: ScrubStrategyUserID,
-			geo:  ScrubStrategyGeoFull,
+			scrubUser: ScrubStrategyUserID,
+			scrubGeo:  ScrubStrategyGeoFull,
 		},
 		{
-			description: "Demographic Buyer ID & Geo Reduced",
+			description: "User ID & Geo Reduced",
 			expected: &openrtb.User{
 				ID:       "",
 				BuyerUID: "",
 				Yob:      42,
 				Gender:   "anyGender",
+				Ext:      json.RawMessage(`{}`),
 				Geo: &openrtb.Geo{
 					Lat:   123.46,
 					Lon:   678.89,
@@ -331,34 +338,68 @@ func TestScrubUser(t *testing.T) {
 					ZIP:   "some zip",
 				},
 			},
-			user: ScrubStrategyUserID,
-			geo:  ScrubStrategyGeoReducedPrecision,
+			scrubUser: ScrubStrategyUserID,
+			scrubGeo:  ScrubStrategyGeoReducedPrecision,
+		},
+		{
+			description: "User ID & Geo None",
+			expected: &openrtb.User{
+				ID:       "",
+				BuyerUID: "",
+				Yob:      42,
+				Gender:   "anyGender",
+				Ext:      json.RawMessage(`{}`),
+				Geo: &openrtb.Geo{
+					Lat:   123.456,
+					Lon:   678.89,
+					Metro: "some metro",
+					City:  "some city",
+					ZIP:   "some zip",
+				},
+			},
+			scrubUser: ScrubStrategyUserID,
+			scrubGeo:  ScrubStrategyGeoNone,
+		},
+		{
+			description: "User None & Geo Full",
+			expected: &openrtb.User{
+				ID:       "anyID",
+				BuyerUID: "anyBuyerUID",
+				Yob:      42,
+				Gender:   "anyGender",
+				Ext:      json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+				Geo:      &openrtb.Geo{},
+			},
+			scrubUser: ScrubStrategyUserNone,
+			scrubGeo:  ScrubStrategyGeoFull,
 		},
 		{
-			description: "Demographic None & Geo None",
+			description: "User None & Geo Reduced",
 			expected: &openrtb.User{
 				ID:       "anyID",
 				BuyerUID: "anyBuyerUID",
 				Yob:      42,
 				Gender:   "anyGender",
+				Ext:      json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
 				Geo: &openrtb.Geo{
-					Lat:   123.456,
+					Lat:   123.46,
 					Lon:   678.89,
 					Metro: "some metro",
 					City:  "some city",
 					ZIP:   "some zip",
 				},
 			},
-			user: ScrubStrategyUserNone,
-			geo:  ScrubStrategyGeoNone,
+			scrubUser: ScrubStrategyUserNone,
+			scrubGeo:  ScrubStrategyGeoReducedPrecision,
 		},
 		{
-			description: "Demographic BuyerIDOnly & Geo None",
+			description: "User None & Geo None",
 			expected: &openrtb.User{
-				ID:       "",
-				BuyerUID: "",
+				ID:       "anyID",
+				BuyerUID: "anyBuyerUID",
 				Yob:      42,
 				Gender:   "anyGender",
+				Ext:      json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
 				Geo: &openrtb.Geo{
 					Lat:   123.456,
 					Lon:   678.89,
@@ -367,13 +408,13 @@ func TestScrubUser(t *testing.T) {
 					ZIP:   "some zip",
 				},
 			},
-			user: ScrubStrategyUserID,
-			geo:  ScrubStrategyGeoNone,
+			scrubUser: ScrubStrategyUserNone,
+			scrubGeo:  ScrubStrategyGeoNone,
 		},
 	}
 
 	for _, test := range testCases {
-		result := NewScrubber().ScrubUser(user, test.user, test.geo)
+		result := NewScrubber().ScrubUser(user, test.scrubUser, test.scrubGeo)
 		assert.Equal(t, test.expected, result, test.description)
 	}
 }
@@ -552,3 +593,92 @@ func TestScrubGeoPrecisionWhenNil(t *testing.T) {
 	result := scrubGeoPrecision(nil)
 	assert.Nil(t, result)
 }
+
+func TestScrubUserExtIDs(t *testing.T) {
+	testCases := []struct {
+		description string
+		userExt     json.RawMessage
+		expected    json.RawMessage
+	}{
+		{
+			description: "Nil",
+			userExt:     nil,
+			expected:    nil,
+		},
+		{
+			description: "Empty String",
+			userExt:     json.RawMessage(``),
+			expected:    json.RawMessage(``),
+		},
+		{
+			description: "Empty Object",
+			userExt:     json.RawMessage(`{}`),
+			expected:    json.RawMessage(`{}`),
+		},
+		{
+			description: "Do Nothing When Malformed",
+			userExt:     json.RawMessage(`malformed`),
+			expected:    json.RawMessage(`malformed`),
+		},
+		{
+			description: "Do Nothing When No IDs Present",
+			userExt:     json.RawMessage(`{"anyExisting":42}}`),
+			expected:    json.RawMessage(`{"anyExisting":42}}`),
+		},
+		{
+			description: "Remove eids + digitrust",
+			userExt:     json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+			expected:    json.RawMessage(`{}`),
+		},
+		{
+			description: "Remove eids + digitrust - With Other Data",
+			userExt:     json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+			expected:    json.RawMessage(`{"anyExisting":42}`),
+		},
+		{
+			description: "Remove eids + digitrust - With Other Nested Data",
+			userExt:     json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+			expected:    json.RawMessage(`{"anyExisting":{"existing":42}}`),
+		},
+		{
+			description: "Remove eids Only",
+			userExt:     json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
+			expected:    json.RawMessage(`{}`),
+		},
+		{
+			description: "Remove eids Only - Empty Array",
+			userExt:     json.RawMessage(`{"eids":[]}`),
+			expected:    json.RawMessage(`{}`),
+		},
+		{
+			description: "Remove eids Only - With Other Data",
+			userExt:     json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
+			expected:    json.RawMessage(`{"anyExisting":42}`),
+		},
+		{
+			description: "Remove eids Only - With Other Nested Data",
+			userExt:     json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`),
+			expected:    json.RawMessage(`{"anyExisting":{"existing":42}}`),
+		},
+		{
+			description: "Remove digitrust Only",
+			userExt:     json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+			expected:    json.RawMessage(`{}`),
+		},
+		{
+			description: "Remove digitrust Only - With Other Data",
+			userExt:     json.RawMessage(`{"anyExisting":42,"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+			expected:    json.RawMessage(`{"anyExisting":42}`),
+		},
+		{
+			description: "Remove digitrust Only - With Other Nested Data",
+			userExt:     json.RawMessage(`{"anyExisting":{"existing":42},"digitrust":{"id":"anyId","keyv":4,"pref":8}}`),
+			expected:    json.RawMessage(`{"anyExisting":{"existing":42}}`),
+		},
+	}
+
+	for _, test := range testCases {
+		result := scrubUserExtIDs(test.userExt)
+		assert.Equal(t, test.expected, result, test.description)
+	}
+}
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 20fce80c..ddd067e8 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -32,6 +32,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderConversant):       syncConfig,
 			string(openrtb_ext.BidderCpmstar):          syncConfig,
 			string(openrtb_ext.BidderDatablocks):       syncConfig,
+			string(openrtb_ext.BidderDmx):              syncConfig,
 			string(openrtb_ext.BidderEmxDigital):       syncConfig,
 			string(openrtb_ext.BidderEngageBDR):        syncConfig,
 			string(openrtb_ext.BidderEPlanning):        syncConfig,
@@ -67,6 +68,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderUcfunnel):         syncConfig,
 			string(openrtb_ext.BidderUnruly):           syncConfig,
 			string(openrtb_ext.BidderValueImpression):  syncConfig,
+			string(openrtb_ext.BidderYieldlab):         syncConfig,
 			string(openrtb_ext.BidderVerizonMedia):     syncConfig,
 			string(openrtb_ext.BidderVisx):             syncConfig,
 			string(openrtb_ext.BidderVrtcal):           syncConfig,