Repository: prebid/prebid-server
Pull request #1693 (2021-02-04 07:07:52)

Don't Load GVL v1 for TCF2 (+ TCF1 Cleanup)
Partially addresses https://github.com/prebid/prebid-server/issues/1689 and separates the TCF1 vs TCF2 fetchers for easy removal of TCF2 in the near future and removal now for TCF1 url construction.


Patch:
diff --git a/.gitignore b/.gitignore
index 79076f9b..514e217b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ vendor
 prebid-server
 build
 debug
+__debug_bin
 
 # config files
 pbs.*
diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go
index 1f81eda0..3a431c9c 100644
--- a/adapters/beachfront/beachfront.go
+++ b/adapters/beachfront/beachfront.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
-	"reflect"
 	"strconv"
 	"strings"
 
@@ -24,12 +23,13 @@ const defaultVideoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id"
 const nurlVideoEndpointSuffix = "&prebidserver"
 
 const beachfrontAdapterName = "BF_PREBID_S2S"
-const beachfrontAdapterVersion = "0.9.1"
+const beachfrontAdapterVersion = "0.9.2"
 
 const minBidFloor = 0.01
 
-const DefaultVideoWidth = 300
-const DefaultVideoHeight = 250
+const defaultVideoWidth = 300
+const defaultVideoHeight = 250
+const fakeIP = "255.255.255.255"
 
 type BeachfrontAdapter struct {
 	bannerEndpoint string
@@ -249,16 +249,12 @@ func getAppId(ext openrtb_ext.ExtImpBeachfront, media openrtb_ext.BidType) (stri
 	var appid string
 	var error error
 
-	if fmt.Sprintf("%s", reflect.TypeOf(ext.AppId)) == "string" &&
-		ext.AppId != "" {
-
+	if ext.AppId != "" {
 		appid = ext.AppId
-	} else if fmt.Sprintf("%s", reflect.TypeOf(ext.AppIds)) == "openrtb_ext.ExtImpBeachfrontAppIds" {
-		if media == openrtb_ext.BidTypeVideo && ext.AppIds.Video != "" {
-			appid = ext.AppIds.Video
-		} else if media == openrtb_ext.BidTypeBanner && ext.AppIds.Banner != "" {
-			appid = ext.AppIds.Banner
-		}
+	} else if media == openrtb_ext.BidTypeVideo && ext.AppIds.Video != "" {
+		appid = ext.AppIds.Video
+	} else if media == openrtb_ext.BidTypeBanner && ext.AppIds.Banner != "" {
+		appid = ext.AppIds.Banner
 	} else {
 		error = errors.New("unable to determine the appId(s) from the supplied extension")
 	}
@@ -317,7 +313,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e
 	}
 
 	if request.Device != nil {
-		bfr.IP = getIP(request.Device.IP)
+		bfr.IP = request.Device.IP
 		bfr.DeviceModel = request.Device.Model
 		bfr.DeviceOs = request.Device.OS
 		if request.Device.DNT != nil {
@@ -399,6 +395,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 		}
 
 		appid, err := getAppId(beachfrontExt, openrtb_ext.BidTypeVideo)
+		bfReqs[i].AppId = appid
 
 		if err != nil {
 			// Failed to get an appid, so this request is junk.
@@ -407,20 +404,25 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 			continue
 		}
 
-		bfReqs[i].AppId = appid
+		bfReqs[i].Request = *request
+		var secure int8
+
+		if bfReqs[i].Request.Device == nil {
+			bfReqs[i].Request.Device = &openrtb.Device{}
+		}
 
-		if beachfrontExt.VideoResponseType != "" {
-			bfReqs[i].VideoResponseType = beachfrontExt.VideoResponseType
+		if beachfrontExt.VideoResponseType == "nurl" {
+			bfReqs[i].VideoResponseType = "nurl"
 		} else {
 			bfReqs[i].VideoResponseType = "adm"
-		}
 
-		bfReqs[i].Request = *request
-		var secure int8
+			if bfReqs[i].Request.Device.IP == "" {
+				bfReqs[i].Request.Device.IP = fakeIP
+			}
+		}
 
 		if bfReqs[i].Request.Site != nil && bfReqs[i].Request.Site.Domain == "" && bfReqs[i].Request.Site.Page != "" {
 			bfReqs[i].Request.Site.Domain = getDomain(bfReqs[i].Request.Site.Page)
-
 			secure = isSecure(bfReqs[i].Request.Site.Page)
 		}
 
@@ -433,7 +435,6 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 						fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0])
 				}
 			}
-
 		}
 
 		if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.DeviceType == 0 {
@@ -454,8 +455,8 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 		}
 
 		if imp.Video.H == 0 && imp.Video.W == 0 {
-			imp.Video.W = DefaultVideoWidth
-			imp.Video.H = DefaultVideoHeight
+			imp.Video.W = defaultVideoWidth
+			imp.Video.H = defaultVideoHeight
 		}
 
 		if len(bfReqs[i].Request.Cur) == 0 {
@@ -467,9 +468,6 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 		bfReqs[i].Request.Imp = make([]openrtb.Imp, 1, 1)
 		bfReqs[i].Request.Imp[0] = imp
 
-		if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.IP != "" {
-			bfReqs[i].Request.Device.IP = getIP(bfReqs[i].Request.Device.IP)
-		}
 	}
 
 	// Strip out any failed requests
@@ -484,15 +482,24 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 
 func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
 	var bids []openrtb.Bid
-	var errs []error
 
+	// The case of response status == 200 and response body length == 2 below covers the case of the banner endpoint returning
+	// an empty JSON array ('[]'), which is functionally no content.
 	if response.StatusCode == http.StatusNoContent || (response.StatusCode == http.StatusOK && len(response.Body) <= 2) {
-		return nil, nil
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("no content or truncated content received from server. status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri),
+		}}
+	}
+
+	if response.StatusCode >= http.StatusInternalServerError {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("server error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri),
+		}}
 	}
 
-	if response.StatusCode == http.StatusBadRequest {
+	if response.StatusCode >= http.StatusBadRequest {
 		return nil, []error{&errortypes.BadInput{
-			Message: fmt.Sprintf("bad request status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri),
+			Message: fmt.Sprintf("request error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri),
 		}}
 	}
 
@@ -501,6 +508,7 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
 	}
 
 	var xtrnal openrtb.BidRequest
+	var errs = make([]error, 0)
 
 	// For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will
 	// only get the User struct and everything else will be nil
@@ -550,18 +558,15 @@ func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) op
 
 func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) {
 	var beachfrontResp []beachfrontResponseSlot
-	var errs = make([]error, 0)
 
 	var openrtbResp openrtb.BidResponse
 
-	// try it as a video
-	if err := json.Unmarshal(response.Body, &openrtbResp); err != nil {
-		errs = append(errs, err)
+	if err := json.Unmarshal(response.Body, &openrtbResp); err != nil || len(openrtbResp.SeatBid) == 0 {
 
-		// try it as a banner
 		if err := json.Unmarshal(response.Body, &beachfrontResp); err != nil {
-			errs = append(errs, err)
-			return nil, errs
+			return nil, []error{&errortypes.BadServerResponse{
+				Message: fmt.Sprint("server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info"),
+			}}
 		} else {
 			return postprocessBanner(beachfrontResp, id)
 		}
@@ -669,18 +674,12 @@ func isSecure(page string) int8 {
 
 }
 
-func getIP(ip string) string {
-	// This will only effect testing. The backend will return "" for localhost IPs,
-	// and seems not to know what IPv6 is, so just setting it to one that is not likely to
-	// be used.
-	if ip == "" || ip == "::1" || ip == "127.0.0.1" {
-		return "192.168.255.255"
+func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest {
+	if len(slice) >= s+1 {
+		return append(slice[:s], slice[s+1:]...)
 	}
-	return ip
-}
 
-func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest {
-	return append(slice[:s], slice[s+1:]...)
+	return []beachfrontVideoRequest{}
 }
 
 // Builder builds a new instance of the Beachfront adapter for the given bidder with the given config.
diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go
new file mode 100644
index 00000000..ebf42843
--- /dev/null
+++ b/adapters/onetag/onetag.go
@@ -0,0 +1,152 @@
+package onetag
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpointTemplate template.Template
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpointTemplate: *template,
+	}
+
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	pubID := ""
+	for idx, imp := range request.Imp {
+		onetagExt, err := getImpressionExt(imp)
+		if err != nil {
+			return nil, []error{err}
+		}
+		if onetagExt.PubId != "" {
+			if pubID == "" {
+				pubID = onetagExt.PubId
+			} else if pubID != onetagExt.PubId {
+				return nil, []error{&errortypes.BadInput{
+					Message: "There must be only one publisher ID",
+				}}
+			}
+		} else {
+			return nil, []error{&errortypes.BadInput{
+				Message: "The publisher ID must not be empty",
+			}}
+		}
+		request.Imp[idx].Ext = onetagExt.Ext
+	}
+
+	url, err := a.buildEndpointURL(pubID)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestData := &adapters.RequestData{
+		Method: "POST",
+		Uri:    url,
+		Body:   requestJSON,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func getImpressionExt(imp openrtb.Imp) (*openrtb_ext.ExtImpOnetag, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Bidder extension not provided or can't be unmarshalled",
+		}
+	}
+
+	var onetagExt openrtb_ext.ExtImpOnetag
+	if err := json.Unmarshal(bidderExt.Bidder, &onetagExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error while unmarshaling bidder extension",
+		}
+	}
+
+	return &onetagExt, nil
+}
+
+func (a *adapter) buildEndpointURL(pubID string) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{PublisherID: pubID}
+	return macros.ResolveMacros(a.endpointTemplate, endpointParams)
+}
+
+func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if responseData.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if responseData.StatusCode != http.StatusOK {
+		err := &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
+		}
+		return nil, []error{err}
+	}
+
+	var response openrtb.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+	for _, seatBid := range response.SeatBid {
+		for _, bid := range seatBid.Bid {
+			bidMediaType, err := getMediaTypeForBid(request.Imp, bid)
+			if err != nil {
+				return nil, []error{err}
+			}
+			b := &adapters.TypedBid{
+				Bid:     &bid,
+				BidType: bidMediaType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, nil
+}
+
+func getMediaTypeForBid(impressions []openrtb.Imp, bid openrtb.Bid) (openrtb_ext.BidType, error) {
+	for _, impression := range impressions {
+		if impression.ID == bid.ImpID {
+			if impression.Banner != nil {
+				return openrtb_ext.BidTypeBanner, nil
+			}
+			if impression.Video != nil {
+				return openrtb_ext.BidTypeVideo, nil
+			}
+			if impression.Native != nil {
+				return openrtb_ext.BidTypeNative, nil
+			}
+		}
+	}
+
+	return "", &errortypes.BadServerResponse{
+		Message: fmt.Sprintf("The impression with ID %s is not present into the request", bid.ImpID),
+	}
+}
diff --git a/adapters/onetag/usersync.go b/adapters/onetag/usersync.go
new file mode 100644
index 00000000..f36b82a8
--- /dev/null
+++ b/adapters/onetag/usersync.go
@@ -0,0 +1,12 @@
+package onetag
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewSyncer(template *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("onetag", 241, template, adapters.SyncTypeIframe)
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index a4c4e222..2dfba673 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -471,7 +471,7 @@ func parseImpressionObject(imp *openrtb.Imp, wrapExt *string, pubID *string) err
 	}
 
 	if *pubID == "" {
-		*pubID = pubmaticExt.PublisherId
+		*pubID = strings.TrimSpace(pubmaticExt.PublisherId)
 	}
 
 	// Parse Wrapper Extension only once per request
diff --git a/adapters/tappx/usersync.go b/adapters/tappx/usersync.go
new file mode 100644
index 00000000..7bf6b9a6
--- /dev/null
+++ b/adapters/tappx/usersync.go
@@ -0,0 +1,12 @@
+package tappx
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewTappxSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("tappx", 628, temp, adapters.SyncTypeIframe)
+}
diff --git a/config/config.go b/config/config.go
old mode 100755
new mode 100644
index ae1f62e9..3a7ad509
--- a/config/config.go
+++ b/config/config.go
@@ -616,6 +616,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D")
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D")
@@ -629,7 +630,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D")
-	// openrtb_ext.BidderTappx doesn't have a good default.
+	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTappx, "https://ssp.api.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -858,6 +859,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs")
 	v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1")
+	v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}")
 	v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid")
 	v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2")
 	v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server")
@@ -879,7 +881,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af")
 	v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server")
 	v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}")
-	v.SetDefault("adapters.tappx.endpoint", "https://{{.Host}}")
+	v.SetDefault("adapters.tappx.endpoint", "http://{{.Host}}")
 	v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid")
 	v.SetDefault("adapters.triplelift_native.disabled", true)
 	v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}")
diff --git a/endpoints/auction.go b/endpoints/auction.go
index 0604c224..92e9769d 100644
--- a/endpoints/auction.go
+++ b/endpoints/auction.go
@@ -189,21 +189,16 @@ func (a *auction) recoverSafely(inner func(*pbs.PBSBidder, metrics.AdapterLabels
 }
 
 func (a *auction) shouldUsersync(ctx context.Context, bidder openrtb_ext.BidderName, gdprPrivacyPolicy gdprPrivacy.Policy) bool {
-	switch gdprPrivacyPolicy.Signal {
-	case "0":
-		return true
-	case "1":
-		if gdprPrivacyPolicy.Consent == "" {
-			return false
-		}
-		fallthrough
-	default:
-		if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprPrivacyPolicy.Consent); !canSync || err != nil {
-			return false
-		}
-		canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprPrivacyPolicy.Consent)
-		return canSync && err == nil
+	gdprSignal := gdpr.SignalAmbiguous
+	if signal, err := gdpr.SignalParse(gdprPrivacyPolicy.Signal); err != nil {
+		gdprSignal = signal
+	}
+
+	if canSync, err := a.gdprPerms.HostCookiesAllowed(ctx, gdprSignal, gdprPrivacyPolicy.Consent); err != nil || !canSync {
+		return false
 	}
+	canSync, err := a.gdprPerms.BidderSyncAllowed(ctx, bidder, gdprSignal, gdprPrivacyPolicy.Consent)
+	return canSync && err == nil
 }
 
 // cache video bids only for Web
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 21876af9..bf3935f0 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -248,13 +248,14 @@ func (req *cookieSyncRequest) filterForGDPR(permissions gdpr.Permissions) {
 		return
 	}
 
-	if allowSync, err := permissions.HostCookiesAllowed(context.Background(), req.Consent); err != nil || !allowSync {
+	// At this point we know the gdpr signal is Yes because the upstream call to parseRequest already denormalized the signal if it was ambiguous
+	if allowSync, err := permissions.HostCookiesAllowed(context.Background(), gdpr.SignalYes, req.Consent); err != nil || !allowSync {
 		req.Bidders = nil
 		return
 	}
 
 	for i := 0; i < len(req.Bidders); i++ {
-		if allowSync, err := permissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(req.Bidders[i]), req.Consent); err != nil || !allowSync {
+		if allowSync, err := permissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(req.Bidders[i]), gdpr.SignalYes, req.Consent); err != nil || !allowSync {
 			req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...)
 			i--
 		}
diff --git a/endpoints/setuid.go b/endpoints/setuid.go
index caa3ae17..4bff02ac 100644
--- a/endpoints/setuid.go
+++ b/endpoints/setuid.go
@@ -143,32 +143,38 @@ func checkChromeBrowserVersion(ua string, index int, chromeStrLength int) bool {
 	return result
 }
 
-func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (bool, int, string) {
-	switch gdprEnabled {
-	case "0":
-		return false, 0, ""
-	case "1":
-		if gdprConsent == "" {
-			return true, http.StatusBadRequest, "gdpr_consent is required when gdpr=1"
-		}
-		fallthrough
-	case "":
-		if allowed, err := perms.HostCookiesAllowed(context.Background(), gdprConsent); err != nil {
-			if _, ok := err.(*gdpr.ErrorMalformedConsent); ok {
-				return true, http.StatusBadRequest, "gdpr_consent was invalid. " + err.Error()
-			} else {
-				// We can't really distinguish between requests that are for a new version of the global vendor list, and
-				// ones which are simply malformed (version number is much too large).
-				// Since we try to fetch new versions as requests come in for them, PBS *should* self-correct
-				// rather quickly, meaning that most of these will be malformed strings.
-				return true, http.StatusBadRequest, "No global vendor list was available to interpret this consent string. If this is a new, valid version, it should become available soon."
-			}
-		} else if !allowed {
-			return true, http.StatusOK, "The gdpr_consent string prevents cookies from being saved"
-		} else {
-			return false, 0, ""
-		}
-	default:
+func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (shouldReturn bool, status int, body string) {
+
+	if gdprEnabled != "" && gdprEnabled != "0" && gdprEnabled != "1" {
 		return true, http.StatusBadRequest, "the gdpr query param must be either 0 or 1. You gave " + gdprEnabled
 	}
+
+	if gdprEnabled == "1" && gdprConsent == "" {
+		return true, http.StatusBadRequest, "gdpr_consent is required when gdpr=1"
+	}
+
+	gdprSignal := gdpr.SignalAmbiguous
+
+	if i, err := strconv.Atoi(gdprEnabled); err == nil {
+		gdprSignal = gdpr.Signal(i)
+	}
+
+	allowed, err := perms.HostCookiesAllowed(context.Background(), gdprSignal, gdprConsent)
+	if err != nil {
+		if _, ok := err.(*gdpr.ErrorMalformedConsent); ok {
+			return true, http.StatusBadRequest, "gdpr_consent was invalid. " + err.Error()
+		}
+
+		// We can't really distinguish between requests that are for a new version of the global vendor list, and
+		// ones which are simply malformed (version number is much too large).
+		// Since we try to fetch new versions as requests come in for them, PBS *should* self-correct
+		// rather quickly, meaning that most of these will be malformed strings.
+		return true, http.StatusBadRequest, "No global vendor list was available to interpret this consent string. If this is a new, valid version, it should become available soon."
+	}
+
+	if allowed {
+		return false, 0, ""
+	}
+
+	return true, http.StatusOK, "The gdpr_consent string prevents cookies from being saved"
 }
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index f05a4d81..1a4bd158 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -62,6 +62,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/nanointeractive"
 	"github.com/prebid/prebid-server/adapters/ninthdecimal"
 	"github.com/prebid/prebid-server/adapters/nobid"
+	"github.com/prebid/prebid-server/adapters/onetag"
 	"github.com/prebid/prebid-server/adapters/openx"
 	"github.com/prebid/prebid-server/adapters/orbidder"
 	"github.com/prebid/prebid-server/adapters/pubmatic"
@@ -165,6 +166,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderNanoInteractive:  nanointeractive.Builder,
 		openrtb_ext.BidderNinthDecimal:     ninthdecimal.Builder,
 		openrtb_ext.BidderNoBid:            nobid.Builder,
+		openrtb_ext.BidderOneTag:           onetag.Builder,
 		openrtb_ext.BidderOpenx:            openx.Builder,
 		openrtb_ext.BidderOrbidder:         orbidder.Builder,
 		openrtb_ext.BidderPubmatic:         pubmatic.Builder,
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index bfc238e1..36dac06c 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -3,9 +3,11 @@ package gdpr
 import (
 	"context"
 	"net/http"
+	"strconv"
 
 	"github.com/prebid/go-gdpr/vendorlist"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
@@ -13,12 +15,12 @@ type Permissions interface {
 	// Determines whether or not the host company is allowed to read/write cookies.
 	//
 	// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
-	HostCookiesAllowed(ctx context.Context, consent string) (bool, error)
+	HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error)
 
 	// Determines whether or not the given bidder is allowed to user personal info for ad targeting.
 	//
 	// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
-	BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error)
+	BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error)
 
 	// Determines whether or not to send PI information to a bidder, or mask it out.
 	//
@@ -42,8 +44,8 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_
 		cfg:       cfg,
 		vendorIDs: vendorIDs,
 		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf1SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf1SpecVersion),
-			tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf2SpecVersion)},
+			tcf1SpecVersion: newVendorListFetcherTCF1(cfg),
+			tcf2SpecVersion: newVendorListFetcherTCF2(ctx, cfg, client, vendorListURLMaker)},
 	}
 
 	if cfg.HostVendorID == 0 {
@@ -65,3 +67,21 @@ type ErrorMalformedConsent struct {
 func (e *ErrorMalformedConsent) Error() string {
 	return "malformed consent string " + e.consent + ": " + e.cause.Error()
 }
+
+// SignalParse parses a raw signal and returns
+func SignalParse(rawSignal string) (Signal, error) {
+	if rawSignal == "" {
+		return SignalAmbiguous, nil
+	}
+
+	i, err := strconv.Atoi(rawSignal)
+
+	if err != nil {
+		return SignalAmbiguous, err
+	}
+	if i != 0 && i != 1 {
+		return SignalAmbiguous, &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"}
+	}
+
+	return Signal(i), nil
+}
diff --git a/gdpr/impl.go b/gdpr/impl.go
index c7998251..06b625da 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -33,20 +33,28 @@ type permissionsImpl struct {
 	fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error)
 }
 
-func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
+	gdprSignal = p.normalizeGDPR(gdprSignal)
+
+	if gdprSignal == SignalNo {
+		return true, nil
+	}
+
 	return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent)
 }
 
-func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) {
+	gdprSignal = p.normalizeGDPR(gdprSignal)
+
+	if gdprSignal == SignalNo {
+		return true, nil
+	}
+
 	id, ok := p.vendorIDs[bidder]
 	if ok {
 		return p.allowSync(ctx, id, consent)
 	}
 
-	if consent == "" {
-		return p.cfg.UsersyncIfAmbiguous, nil
-	}
-
 	return false, nil
 }
 
@@ -55,13 +63,7 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt
 		return true, true, true, nil
 	}
 
-	if gdprSignal == SignalAmbiguous {
-		if p.cfg.UsersyncIfAmbiguous {
-			gdprSignal = SignalNo
-		} else {
-			gdprSignal = SignalYes
-		}
-	}
+	gdprSignal = p.normalizeGDPR(gdprSignal)
 
 	if gdprSignal == SignalNo {
 		return true, true, true, nil
@@ -82,10 +84,22 @@ func (p *permissionsImpl) defaultVendorPermissions() (allowPI bool, allowGeo boo
 	return false, false, false, nil
 }
 
+func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal {
+	if gdprSignal != SignalAmbiguous {
+		return gdprSignal
+	}
+
+	if p.cfg.UsersyncIfAmbiguous {
+		return SignalNo
+	}
+
+	return SignalYes
+}
+
 func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (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 false, nil
 	}
 
 	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent)
@@ -229,18 +243,18 @@ type AllowHostCookies struct {
 }
 
 // HostCookiesAllowed always returns true
-func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
 	return true, nil
 }
 
 // Exporting to allow for easy test setups
 type AlwaysAllow struct{}
 
-func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
 	return true, nil
 }
 
-func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) {
 	return true, nil
 }
 
@@ -251,11 +265,11 @@ func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext
 // Exporting to allow for easy test setups
 type AlwaysFail struct{}
 
-func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
 	return false, nil
 }
 
-func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) {
 	return false, nil
 }
 
diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go
index 95a17109..bc7eab40 100644
--- a/gdpr/vendorlist-fetching.go
+++ b/gdpr/vendorlist-fetching.go
@@ -26,30 +26,40 @@ type saveVendors func(uint16, api.VendorList)
 //
 // Nothing in this file is exported. Public APIs can be found in gdpr.go
 
-func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, tcfSpecVersion uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
-	var fallback api.VendorList
-
-	if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) == 0 {
-		return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
+func newVendorListFetcherTCF1(cfg config.GDPR) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+	if len(cfg.TCF1.FallbackGVLPath) == 0 {
+		return func(_ context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
 			return nil, makeVendorListNotFoundError(vendorListVersion)
 		}
 	}
 
-	if tcfSpecVersion == tcf1SpecVersion {
-		fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath)
+	fallback := loadFallbackGVLForTCF1(cfg.TCF1.FallbackGVLPath)
+	return func(_ context.Context, _ uint16) (vendorlist.VendorList, error) {
+		return fallback, nil
+	}
+}
 
-		return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
-			return fallback, nil
-		}
+func loadFallbackGVLForTCF1(fallbackGVLPath string) vendorlist.VendorList {
+	fallbackContents, err := ioutil.ReadFile(fallbackGVLPath)
+	if err != nil {
+		glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err)
+	}
+
+	fallback, err := vendorlist.ParseEagerly(fallbackContents)
+	if err != nil {
+		glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err)
 	}
+	return fallback
+}
 
-	cacheSave, cacheLoad := newVendorListCache(fallback)
+func newVendorListFetcherTCF2(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+	cacheSave, cacheLoad := newVendorListCache()
 
 	preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
 	defer cancel()
-	preloadCache(preloadContext, client, urlMaker, cacheSave, tcfSpecVersion)
+	preloadCache(preloadContext, client, urlMaker, cacheSave)
 
-	saveOneRateLimited := newOccasionalSaver(cfg.Timeouts.ActiveTimeout(), tcfSpecVersion)
+	saveOneRateLimited := newOccasionalSaver(cfg.Timeouts.ActiveTimeout())
 	return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
 		// Attempt To Load From Cache
 		if list := cacheLoad(vendorListVersion); list != nil {
@@ -58,7 +68,7 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http
 
 		// Attempt To Download
 		// - May not add to cache immediately.
-		saveOneRateLimited(ctx, client, urlMaker(vendorListVersion, tcfSpecVersion), cacheSave)
+		saveOneRateLimited(ctx, client, urlMaker(vendorListVersion), cacheSave)
 
 		// Attempt To Load From Cache Again
 		// - May have been added by the call to saveOneRateLimited.
@@ -66,11 +76,6 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http
 			return list, nil
 		}
 
-		// Attempt To Use Hardcoded Fallback
-		if fallback != nil {
-			return fallback, nil
-		}
-
 		// Give Up
 		return nil, makeVendorListNotFoundError(vendorListVersion)
 	}
@@ -81,27 +86,24 @@ func makeVendorListNotFoundError(vendorListVersion uint16) error {
 }
 
 // preloadCache saves all the known versions of the vendor list for future use.
-func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16, uint8) string, saver saveVendors, tcfSpecVersion uint8) {
-	latestVersion := saveOne(ctx, client, urlMaker(0, tcfSpecVersion), saver, tcfSpecVersion)
+func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16) string, saver saveVendors) {
+	latestVersion := saveOne(ctx, client, urlMaker(0), saver)
 
-	for i := uint16(1); i < latestVersion; i++ {
-		saveOne(ctx, client, urlMaker(i, tcfSpecVersion), saver, tcfSpecVersion)
+	// The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it.
+	firstVersionToLoad := uint16(2)
+
+	for i := firstVersionToLoad; i < latestVersion; i++ {
+		saveOne(ctx, client, urlMaker(i), saver)
 	}
 }
 
 // Make a URL which can be used to fetch a given version of the Global Vendor List. If the version is 0,
 // this will fetch the latest version.
-func vendorListURLMaker(vendorListVersion uint16, tcfSpecVersion uint8) string {
-	if tcfSpecVersion == tcf2SpecVersion {
-		if vendorListVersion == 0 {
-			return "https://vendor-list.consensu.org/v2/vendor-list.json"
-		}
-		return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json"
-	}
+func vendorListURLMaker(vendorListVersion uint16) string {
 	if vendorListVersion == 0 {
-		return "https://vendor-list.consensu.org/vendorlist.json"
+		return "https://vendor-list.consensu.org/v2/vendor-list.json"
 	}
-	return "https://vendor-list.consensu.org/v-" + strconv.Itoa(int(vendorListVersion)) + "/vendorlist.json"
+	return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json"
 }
 
 // newOccasionalSaver returns a wrapped version of saveOne() which only activates every few minutes.
@@ -109,7 +111,7 @@ func vendorListURLMaker(vendorListVersion uint16, tcfSpecVersion uint8) string {
 // The goal here is to update quickly when new versions of the VendorList are released, but not wreck
 // server performance if a bad CMP starts sending us malformed consent strings that advertize a version
 // that doesn't exist yet.
-func newOccasionalSaver(timeout time.Duration, tcfSpecVersion uint8) func(ctx context.Context, client *http.Client, url string, saver saveVendors) {
+func newOccasionalSaver(timeout time.Duration) func(ctx context.Context, client *http.Client, url string, saver saveVendors) {
 	lastSaved := &atomic.Value{}
 	lastSaved.Store(time.Time{})
 
@@ -120,13 +122,13 @@ func newOccasionalSaver(timeout time.Duration, tcfSpecVersion uint8) func(ctx co
 		if timeSinceLastSave.Minutes() > 10 {
 			withTimeout, cancel := context.WithTimeout(ctx, timeout)
 			defer cancel()
-			saveOne(withTimeout, client, url, saver, tcfSpecVersion)
+			saveOne(withTimeout, client, url, saver)
 			lastSaved.Store(now)
 		}
 	}
 }
 
-func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors, tcfSpecVersion uint8) uint16 {
+func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors) uint16 {
 	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		glog.Errorf("Failed to build GET %s request. Cookie syncs may be affected: %v", url, err)
@@ -150,11 +152,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen
 		return 0
 	}
 	var newList api.VendorList
-	if tcfSpecVersion == tcf2SpecVersion {
-		newList, err = vendorlist2.ParseEagerly(respBody)
-	} else {
-		newList, err = vendorlist.ParseEagerly(respBody)
-	}
+	newList, err = vendorlist2.ParseEagerly(respBody)
 	if err != nil {
 		glog.Errorf("GET %s returned malformed JSON. Cookie syncs may be affected. Error was %v. Body was %s", url, err, string(respBody))
 		return 0
@@ -164,7 +162,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen
 	return newList.Version()
 }
 
-func newVendorListCache(fallbackVL api.VendorList) (save func(vendorListVersion uint16, list api.VendorList), load func(vendorListVersion uint16) api.VendorList) {
+func newVendorListCache() (save func(vendorListVersion uint16, list api.VendorList), load func(vendorListVersion uint16) api.VendorList) {
 	cache := &sync.Map{}
 
 	save = func(vendorListVersion uint16, list api.VendorList) {
@@ -180,16 +178,3 @@ func newVendorListCache(fallbackVL api.VendorList) (save func(vendorListVersion
 	}
 	return
 }
-
-func loadFallbackGVL(fallbackGVLPath string) vendorlist.VendorList {
-	fallbackContents, err := ioutil.ReadFile(fallbackGVLPath)
-	if err != nil {
-		glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err)
-	}
-
-	fallback, err := vendorlist.ParseEagerly(fallbackContents)
-	if err != nil {
-		glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err)
-	}
-	return fallback
-}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 576b1d1a..83cf0c96 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -102,6 +102,7 @@ const (
 	BidderNanoInteractive  BidderName = "nanointeractive"
 	BidderNinthDecimal     BidderName = "ninthdecimal"
 	BidderNoBid            BidderName = "nobid"
+	BidderOneTag           BidderName = "onetag"
 	BidderOpenx            BidderName = "openx"
 	BidderOrbidder         BidderName = "orbidder"
 	BidderPubmatic         BidderName = "pubmatic"
@@ -203,6 +204,7 @@ func CoreBidderNames() []BidderName {
 		BidderNanoInteractive,
 		BidderNinthDecimal,
 		BidderNoBid,
+		BidderOneTag,
 		BidderOpenx,
 		BidderOrbidder,
 		BidderPubmatic,
diff --git a/openrtb_ext/imp_onetag.go b/openrtb_ext/imp_onetag.go
new file mode 100644
index 00000000..47d06ed4
--- /dev/null
+++ b/openrtb_ext/imp_onetag.go
@@ -0,0 +1,10 @@
+package openrtb_ext
+
+import (
+	"encoding/json"
+)
+
+type ExtImpOnetag struct {
+	PubId string          `json:"pubId"`
+	Ext   json.RawMessage `json:"ext"`
+}
diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml
new file mode 100644
index 00000000..fedaabf9
--- /dev/null
+++ b/static/bidder-info/onetag.yaml
@@ -0,0 +1,13 @@
+maintainer:
+  email: devops@onetag.com
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
\ No newline at end of file
diff --git a/static/bidder-info/tappx.yaml b/static/bidder-info/tappx.yaml
index 4c8d1560..527959b7 100644
--- a/static/bidder-info/tappx.yaml
+++ b/static/bidder-info/tappx.yaml
@@ -5,3 +5,7 @@ capabilities:
     mediaTypes:
       - banner
       - video
+  site:
+    mediaTypes:
+      - banner
+      - video
\ No newline at end of file
diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json
index f0b8c7a6..6c8a6773 100644
--- a/static/bidder-params/adform.json
+++ b/static/bidder-params/adform.json
@@ -25,7 +25,7 @@
     },
     "cdims": {
       "type": "string",
-      "description": "Comma-separated creative dimentions.",
+      "description": "Comma-separated creative dimensions.",
       "pattern": "(^\\d+x\\d+)(,\\d+x\\d+)*$"
     },
     "minp": {
diff --git a/static/bidder-params/nobid.json b/static/bidder-params/nobid.json
index 576dbfec..6293c40c 100644
--- a/static/bidder-params/nobid.json
+++ b/static/bidder-params/nobid.json
@@ -7,12 +7,12 @@
     "properties": {
       "siteId": {
         "type": "integer",
-        "description": "A Required ID which identifies the NoBid site. The siteId paramerter is provided by your NoBid account manager."
+        "description": "A Required ID which identifies the NoBid site. The siteId parameter is provided by your NoBid account manager."
       }, "placementId": { 
     	  "type": "integer",
-    	  "description": "An oprional ID which identifies an adunit in a site. The placementId paramerter is provided by your NoBid account manager."
+    	  "description": "An optional ID which identifies an adunit in a site. The placementId parameter is provided by your NoBid account manager."
       }
     },
     "required": ["siteId"]
   }
-  
\ No newline at end of file
+  
diff --git a/static/bidder-params/onetag.json b/static/bidder-params/onetag.json
new file mode 100644
index 00000000..ca2911ba
--- /dev/null
+++ b/static/bidder-params/onetag.json
@@ -0,0 +1,20 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Onetag Adapter Params",
+    "description": "A schema which validates params accepted by the Onetag adapter",
+    "type": "object",
+  
+    "properties": {
+        "pubId": {
+            "type": "string",
+            "minLength": 1,
+            "description": "The publisher’s ID provided by OneTag"
+        },
+        "ext": {
+            "type": "object",
+            "description": "A set of custom key-value pairs"
+        }
+    },
+
+    "required": ["pubId"]
+}
\ No newline at end of file
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
old mode 100755
new mode 100644
index c41f7c6c..1d31d7d1
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -55,6 +55,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/nanointeractive"
 	"github.com/prebid/prebid-server/adapters/ninthdecimal"
 	"github.com/prebid/prebid-server/adapters/nobid"
+	"github.com/prebid/prebid-server/adapters/onetag"
 	"github.com/prebid/prebid-server/adapters/openx"
 	"github.com/prebid/prebid-server/adapters/pubmatic"
 	"github.com/prebid/prebid-server/adapters/pulsepoint"
@@ -69,6 +70,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/sonobi"
 	"github.com/prebid/prebid-server/adapters/sovrn"
 	"github.com/prebid/prebid-server/adapters/synacormedia"
+	"github.com/prebid/prebid-server/adapters/tappx"
 	"github.com/prebid/prebid-server/adapters/telaria"
 	"github.com/prebid/prebid-server/adapters/triplelift"
 	"github.com/prebid/prebid-server/adapters/triplelift_native"
@@ -142,6 +144,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderNoBid, nobid.NewNoBidSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer)
@@ -156,6 +159,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer)


Test patch:
diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json
new file mode 100644
index 00000000..26b050f2
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json
@@ -0,0 +1,123 @@
+{
+  "mockBidRequest": {
+    "id": "adm-video",
+    "imp": [
+      {
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 3.01,
+            "appId": "videoAppId1"
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        }
+      }
+    ],
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "device":{
+      "ip":"192.168.168.168"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1",
+        "body": {
+          "id": "adm-video",
+          "imp": [
+            {
+              "video": {
+                "w": 300,
+                "h": 250,
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "bidfloor": 3.01,
+              "id": "video1",
+              "secure": 1
+            }
+          ],
+          "site": {
+            "page": "https://some.domain.us/some/page.html",
+            "domain": "some.domain.us"
+          },
+          "cur": [
+            "USD"
+          ],
+          "device":{
+            "devicetype": 2,
+            "ip":"192.168.168.168"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "adm-video",
+          "seatBid": [
+            {
+              "bid": [
+                {
+                  "id": "5fd7c8a6ff2f1f0d42ee6427",
+                  "impid": "video1",
+                  "price": 20,
+                  "adm": "<VAST version=\"2.0\"><Ad><Wrapper>http://example.com/vast.xml</Wrapper></Ad></VAST>",
+                  "adid": "1088",
+                  "adomain": [
+                    "beachfront.io"
+                  ],
+                  "cid": "277",
+                  "crid": "532",
+                  "w": 300,
+                  "h": 250,
+                  "ext": {
+                    "duration": 30
+                  }
+                }
+              ],
+              "seat": "bfio-s-1"
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "video1AdmVideo",
+            "impid": "video1",
+            "price": 20,
+            "adm": "<VAST version=\"2.0\"><Ad><Wrapper>http://example.com/vast.xml</Wrapper></Ad></VAST>",
+            "adid": "1088",
+            "adomain": [
+              "beachfront.io"
+            ],
+            "cid": "277",
+            "crid": "532",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "duration": 30
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/exemplary/banner.json b/adapters/beachfront/beachfronttest/exemplary/banner.json
index 1eb17286..e3dcffa3 100644
--- a/adapters/beachfront/beachfronttest/exemplary/banner.json
+++ b/adapters/beachfront/beachfronttest/exemplary/banner.json
@@ -56,7 +56,7 @@
           "dnt": 0,
           "ua": "",
           "adapterName": "BF_PREBID_S2S",
-          "adapterVersion": "0.9.1",
+          "adapterVersion": "0.9.2",
           "user": {}
         }
       },
diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json
similarity index 100%
rename from adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json
rename to adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json
diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json
similarity index 98%
rename from adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json
rename to adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json
index d3fa41a2..842e0f37 100644
--- a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json
+++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json
@@ -22,9 +22,6 @@
     ],
     "site": {
       "page": "https://some.domain.us/some/page.html"
-    },
-    "device":{
-      "ip":"255.255.255.255"
     }
   },
 
diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json
similarity index 95%
rename from adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json
rename to adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json
index 3482f4ef..c326a33a 100644
--- a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json
@@ -34,6 +34,9 @@
     ],
     "site": {
       "page": "https://some.domain.us/some/page.html"
+    },
+    "device":{
+      "ip":"255.255.255.255"
     }
   },
 
@@ -64,11 +67,11 @@
           "deviceModel": "",
           "isMobile": 0,
           "ua": "",
-          "ip": "",
+          "ip": "255.255.255.255",
           "dnt": 0,
           "user": {},
           "adapterName": "BF_PREBID_S2S",
-          "adapterVersion": "0.9.1",
+          "adapterVersion": "0.9.2",
           "requestId": "banner-and-video"
         }
       },
@@ -109,6 +112,10 @@
             "domain": "some.domain.us",
             "page": "https://some.domain.us/some/page.html"
           },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
           "cur": [
             "USD"
           ]
diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json
similarity index 95%
rename from adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json
rename to adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json
index bff1b76a..f67903d0 100644
--- a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json
@@ -33,6 +33,9 @@
     ],
     "site": {
       "page": "https://some.domain.us/some/page.html"
+    },
+    "device": {
+      "ip": "255.255.255.255"
     }
   },
 
@@ -63,11 +66,11 @@
           "deviceModel": "",
           "isMobile": 0,
           "ua": "",
-          "ip": "",
+          "ip": "255.255.255.255",
           "dnt": 0,
           "user": {},
           "adapterName": "BF_PREBID_S2S",
-          "adapterVersion": "0.9.1",
+          "adapterVersion": "0.9.2",
           "requestId": "banner-and-video"
         }
       },
@@ -108,6 +111,10 @@
             "domain": "some.domain.us",
             "page": "https://some.domain.us/some/page.html"
           },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
           "cur": [
             "USD"
           ]
diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json
similarity index 97%
rename from adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json
rename to adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json
index 9db714f4..b950d734 100644
--- a/adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json
@@ -68,7 +68,7 @@
           "dnt": 0,
           "user": {},
           "adapterName": "BF_PREBID_S2S",
-          "adapterVersion": "0.9.1",
+          "adapterVersion": "0.9.2",
           "requestId": "banner-and-video"
         }
       },
@@ -110,6 +110,9 @@
             "page": "https://some.domain.us/some/page.html"
           },
           "isPrebid": true,
+          "device": {
+            "devicetype": 2
+          },
           "cur": [
             "USD"
           ]
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json
new file mode 100644
index 00000000..1771f393
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json
@@ -0,0 +1,76 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": [
+      {
+        "bidfloor": 0.02,
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "bidfloor": 5.02,
+            "appId": "dudAppId"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/prebid_display",
+        "body": {
+          "slots": [
+            {
+              "slot": "",
+              "id": "dudAppId",
+              "bidfloor": 5.02,
+              "sizes": [
+                {
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ],
+          "domain": "some.domain.us",
+          "page": "https://some.domain.us/some/page.html",
+          "referrer": "",
+          "search": "",
+          "secure": 1,
+          "requestId": "some_test_ad",
+          "isMobile": 0,
+          "ip": "",
+          "deviceModel": "",
+          "deviceOs": "",
+          "dnt": 0,
+          "ua": "",
+          "adapterName": "BF_PREBID_S2S",
+          "adapterVersion": "0.9.2",
+          "user": {}
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": ""
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "request error status code 400 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json
index c90c1215..8e607f67 100644
--- a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json
@@ -57,7 +57,7 @@
           "dnt": 0,
           "ua": "",
           "adapterName": "BF_PREBID_S2S",
-          "adapterVersion": "0.9.1",
+          "adapterVersion": "0.9.2",
           "user": {
           }
         }
@@ -69,5 +69,12 @@
     }
   ],
 
-  "expectedBidResponses": []
+  "expectedBidResponses": [],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "no content or truncated content received from server. status code 200 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
 }
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json b/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json
new file mode 100644
index 00000000..a53dd8ce
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json
@@ -0,0 +1,34 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": [
+      {
+        "bidfloor": 0.02,
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.02,
+            "appId": ""
+          }
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "unable to determine the appId(s) from the supplied extension",
+      "comparison": "literal"
+    }
+  ] 
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json b/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json
new file mode 100644
index 00000000..3b582648
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json
@@ -0,0 +1,36 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": [
+      {
+        "bidfloor": 0.02,
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.02,
+            "appIds": {
+              "video": "theWrongAppId"
+            } 
+          }
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "unable to determine the appId(s) from the supplied extension",
+      "comparison": "literal"
+    }
+  ] 
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json
new file mode 100644
index 00000000..51c676f5
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json
@@ -0,0 +1,80 @@
+{
+  "mockBidRequest": {
+    "id": "adm-video",
+    "imp": [
+      {
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 3.01,
+            "appId": "videoAppId1"
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        }
+      }
+    ],
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "device":{
+      "ip":"255.255.255.255"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1",
+        "body": {
+          "id": "adm-video",
+          "imp": [
+            {
+              "video": {
+                "w": 300,
+                "h": 250,
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "bidfloor": 3.01,
+              "id": "video1",
+              "secure": 1
+            }
+          ],
+          "site": {
+            "page": "https://some.domain.us/some/page.html",
+            "domain": "some.domain.us"
+          },
+          "cur": [
+            "USD"
+          ],
+          "device":{
+            "devicetype": 2,
+            "ip":"255.255.255.255"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+            "wrong": "very very wrong.",
+            "alsoWrong": "not even close to right."
+        }
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json
new file mode 100644
index 00000000..b751f763
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json
@@ -0,0 +1,77 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": [
+      {
+        "bidfloor": 0.02,
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.02,
+            "appId": "bannerAppId1"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/prebid_display",
+        "body": {
+          "slots": [
+            {
+              "slot": "",
+              "id": "bannerAppId1",
+              "bidfloor": 0.02,
+              "sizes": [
+                {
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ],
+          "domain": "some.domain.us",
+          "page": "https://some.domain.us/some/page.html",
+          "referrer": "",
+          "search": "",
+          "secure": 1,
+          "requestId": "some_test_ad",
+          "isMobile": 0,
+          "ip": "",
+          "deviceModel": "",
+          "deviceOs": "",
+          "dnt": 0,
+          "ua": "",
+          "adapterName": "BF_PREBID_S2S",
+          "adapterVersion": "0.9.2",
+          "user": {}
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+            "wrong": "very very wrong.",
+            "alsoWrong": "not even close to right."
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json
new file mode 100644
index 00000000..7990b0a1
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json
@@ -0,0 +1,82 @@
+{
+  "mockBidRequest": {
+    "id": "adm-video",
+    "imp": [
+      {
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 3.01,
+            "appId": "videoAppId1",
+            "videoResponseType": "nurl"
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        }
+      }
+    ],
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "device":{
+      "ip":"255.255.255.255"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1&prebidserver",
+        "body": {
+          "id": "adm-video",
+          "imp": [
+            {
+              "video": {
+                "w": 300,
+                "h": 250,
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "bidfloor": 3.01,
+              "id": "video1",
+              "secure": 1
+            }
+          ],
+          "isPrebid": true,
+          "site": {
+            "page": "https://some.domain.us/some/page.html",
+            "domain": "some.domain.us"
+          },
+          "cur": [
+            "USD"
+          ],
+          "device":{
+            "devicetype": 2,
+            "ip":"255.255.255.255"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+            "wrong": "very very wrong.",
+            "alsoWrong": "not even close to right."
+        }
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json
new file mode 100644
index 00000000..f5d4e822
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json
@@ -0,0 +1,96 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": [
+      {
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.002,
+            "appId": "bannerAppId1"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/prebid_display",
+        "body": {
+          "slots": [
+            {
+              "slot": "",
+              "id": "bannerAppId1",
+              "bidfloor": 0,
+              "sizes": [
+                {
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ],
+          "domain": "some.domain.us",
+          "page": "https://some.domain.us/some/page.html",
+          "referrer": "",
+          "search": "",
+          "secure": 1,
+          "requestId": "some_test_ad",
+          "isMobile": 0,
+          "ip": "",
+          "deviceModel": "",
+          "deviceOs": "",
+          "dnt": 0,
+          "ua": "",
+          "adapterName": "BF_PREBID_S2S",
+          "adapterVersion": "0.9.2",
+          "user": {}
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "crid": "crid_1",
+            "price": 2.942808,
+            "w": 300,
+            "h": 250,
+            "slot": "div-gpt-ad-1460505748561-0",
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>"
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "div-gpt-ad-1460505748561-0Banner",
+            "impid": "div-gpt-ad-1460505748561-0",
+            "price": 2.942808,
+            "adm": "<div id=\"44861168\"><script>!function(){console.log\"Hello, ad.\";}();<\/script><\/div>",
+            "crid": "crid_1",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/beachfront/beachfronttest/supplemental/internal-server-error-500.json b/adapters/beachfront/beachfronttest/supplemental/internal-server-error-500.json
new file mode 100644
index 00000000..7344db12
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/internal-server-error-500.json
@@ -0,0 +1,78 @@
+{
+  "mockBidRequest": {
+    "id": "nurl-video",
+    "imp": [
+      {
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "videoResponseType": "nurl",
+            "bidfloor": 0.01,
+            "appId": "videoAppId1"
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        }
+      }
+    ],
+    "site": {
+      "page": "http://example.com/whatever/something.html"
+    },
+    "device": {
+      "ip": "255.255.255.255"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1&prebidserver",
+        "body": {
+          "id": "nurl-video",
+          "isPrebid": true,
+          "imp": [
+            {
+              "video": {
+                "w": 300,
+                "h": 250,
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "id": "video1",
+              "secure": 0
+            }
+          ],
+          "site": {
+            "page": "http://example.com/whatever/something.html",
+            "domain": "example.com"
+          },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
+          "cur": [
+            "USD"
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 500,
+        "body": ""
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "server error status code 500 from https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1&prebidserver. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/no-imps.json b/adapters/beachfront/beachfronttest/supplemental/no-imps.json
new file mode 100644
index 00000000..fcbe347c
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/no-imps.json
@@ -0,0 +1,16 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": []
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "no valid impressions were found in the request",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo--response-order--banner-adm-nurl--PASS.json b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json
similarity index 95%
rename from adapters/beachfront/beachfronttest/supplemental/six-nine-combo--response-order--banner-adm-nurl--PASS.json
rename to adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json
index 56af3df3..4c009ff0 100644
--- a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo--response-order--banner-adm-nurl--PASS.json
+++ b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json
@@ -130,6 +130,9 @@
     ],
     "site": {
       "page": "http://example.com/whatever/something.html"
+    },
+    "device":{
+      "ip":"255.255.255.255"
     }
   } ,
   "httpCalls": [
@@ -196,8 +199,8 @@
           "dnt": 0,
           "user": {},
           "adapterName": "BF_PREBID_S2S",
-          "adapterVersion": "0.9.1",
-          "ip": "",
+          "adapterVersion": "0.9.2",
+          "ip": "255.255.255.255",
           "requestId": "six-nine-combo"
         }
       },
@@ -262,6 +265,10 @@
             "domain": "example.com",
             "page": "http://example.com/whatever/something.html"
           },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
           "cur": [
             "USD"
           ]
@@ -316,6 +323,10 @@
             "domain": "example.com",
             "page": "http://example.com/whatever/something.html"
           },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
           "cur": [
             "USD"
           ]
@@ -370,6 +381,10 @@
             "domain": "example.com",
             "page": "http://example.com/whatever/something.html"
           },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
           "cur": [
             "USD"
           ]
@@ -425,6 +440,10 @@
             "domain": "example.com",
             "page": "http://example.com/whatever/something.html"
           },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
           "cur": [
             "USD"
           ]
diff --git a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json
index bff475ff..d48962e6 100644
--- a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json
+++ b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json
@@ -109,7 +109,7 @@
 
           },
           "adapterName":"BF_PREBID_S2S",
-          "adapterVersion":"0.9.1",
+          "adapterVersion":"0.9.2",
           "ip":"255.255.255.255",
           "requestId":"two-four-combo"
 
diff --git a/adapters/beachfront/beachfronttest/supplemental/video-no-appid.json b/adapters/beachfront/beachfronttest/supplemental/video-no-appid.json
new file mode 100644
index 00000000..e4803ced
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/video-no-appid.json
@@ -0,0 +1,38 @@
+{
+  "mockBidRequest": {
+    "id": "nurl-video",
+    "imp": [
+      {
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "videoResponseType": "nurl",
+            "bidfloor": 0.01,
+            "appId": ""
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        }
+      }
+    ],
+    "site": {
+      "page": "http://example.com/whatever/something.html"
+    },
+    "device": {
+      "ip": "255.255.255.255"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "unable to determine the appId(s) from the supplied extension",
+      "comparison": "literal"
+    }
+  ] 
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/video-wrong-appids.json b/adapters/beachfront/beachfronttest/supplemental/video-wrong-appids.json
new file mode 100644
index 00000000..50763d87
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/video-wrong-appids.json
@@ -0,0 +1,39 @@
+{
+  "mockBidRequest": {
+    "id": "adm-video",
+    "imp": [
+      {
+        "id": "video1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 3.01,
+            "appIds": {
+              "banner": "theWrongAppId"
+            }
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        }
+      }
+    ],
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "device":{
+      "ip":"255.255.255.255"
+    }
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "unable to determine the appId(s) from the supplied extension",
+      "comparison": "literal"
+    }
+  ] 
+}
diff --git a/adapters/onetag/onetag_test.go b/adapters/onetag/onetag_test.go
new file mode 100644
index 00000000..9f7c8e50
--- /dev/null
+++ b/adapters/onetag/onetag_test.go
@@ -0,0 +1,26 @@
+package onetag
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{
+		Endpoint: "{{Malformed}}"})
+
+	assert.Error(t, buildErr)
+}
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{
+		Endpoint: "https://example.com/prebid-server/{{.PublisherID}}"})
+
+	assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr)
+
+	adapterstest.RunJSONBidderTest(t, "onetagtest", bidder)
+}
diff --git a/adapters/onetag/onetagtest/exemplary/no-bid.json b/adapters/onetag/onetagtest/exemplary/no-bid.json
new file mode 100644
index 00000000..012834ca
--- /dev/null
+++ b/adapters/onetag/onetagtest/exemplary/no-bid.json
@@ -0,0 +1,78 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "device": {
+        "ip": "123.123.123.123",
+        "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      },
+      "app": {
+        "id": "1",
+        "bundle": "com.wls.testwlsapplication"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "instl": 1,
+          "video": {
+            "placement": 3,
+            "mimes": [
+              "video/mp4"
+            ],
+            "protocols": [
+              2,
+              5
+            ],
+            "w": 1024,
+            "h": 576
+          },
+          "ext": {
+            "bidder": {
+              "pubId": "386276e072"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://example.com/prebid-server/386276e072",
+          "headers": {},
+          "body": {
+            "id": "test-request-id",
+            "device": {
+              "ip": "123.123.123.123",
+              "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+            },
+            "app": {
+              "id": "1",
+              "bundle": "com.wls.testwlsapplication"
+            },
+            "imp": [
+              {
+                "id": "test-imp-id",
+                "instl": 1,
+                "video": {
+                  "placement": 3,
+                  "mimes": [
+                    "video/mp4"
+                  ],
+                  "protocols": [
+                    2,
+                    5
+                  ],
+                  "w": 1024,
+                  "h": 576
+                }
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204,
+          "body": {}
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/exemplary/simple-banner.json b/adapters/onetag/onetagtest/exemplary/simple-banner.json
new file mode 100644
index 00000000..7489a27d
--- /dev/null
+++ b/adapters/onetag/onetagtest/exemplary/simple-banner.json
@@ -0,0 +1,201 @@
+{
+   "mockBidRequest": {
+     "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+     "imp": [
+       {
+         "id": "121-dt1",
+         "banner": {
+           "h": 250,
+           "w": 300,
+           "pos": 1
+         },
+         "bidfloor": 0.05,
+         "ext": {
+            "bidder": {
+              "pubId": "386276e072"
+            }
+          }
+        },
+       {
+         "id": "121-dt2",
+         "banner": {
+           "h": 728,
+           "w": 90,
+           "pos": 0
+         },
+         "bidfloor": 0.12,
+         "ext": {
+            "bidder": {
+              "pubId": "386276e072",
+              "ext": {
+                "key1": "value1",
+                "key2": "value2"
+              }
+            }
+          }
+       }
+     ],
+     "site": {
+       "id": "15047",
+       "domain": "dailymotion.com",
+       "cat": ["IAB1"],
+       "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+       "publisher": {
+         "id": "8796",
+         "name": "dailymotion",
+         "cat": ["IAB3-1"],
+         "domain": "dailymotion.com"
+       }
+     },
+     "user": {
+       "id": "518c3da3717203f34019b038"
+     },
+     "device": {
+       "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+       "ip": "123.145.167.189"
+     },
+     "at": 1,
+     "cur": [
+       "USD"
+     ]
+   },
+   "httpCalls": [
+     {
+       "expectedRequest": {
+         "uri": "https://example.com/prebid-server/386276e072",
+         "headers": {},
+         "body": {
+           "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+           "imp": [
+             {
+               "id": "121-dt1",
+               "banner": {
+                 "h": 250,
+                 "w": 300,
+                 "pos": 1
+               },
+               "bidfloor": 0.05
+             },
+             {
+               "id": "121-dt2",
+               "banner": {
+                 "h": 728,
+                 "w": 90,
+                 "pos": 0
+               },
+               "bidfloor": 0.12,
+               "ext": {
+                  "key1": "value1",
+                  "key2": "value2"
+               }
+             }
+           ],
+           "site": {
+             "id": "15047",
+             "domain": "dailymotion.com",
+             "cat": ["IAB1"],
+             "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+             "publisher": {
+               "id": "8796",
+               "name": "dailymotion",
+               "cat": ["IAB3-1"],
+               "domain": "dailymotion.com"
+             }
+           },
+           "user": {
+             "id": "518c3da3717203f34019b038"
+           },
+           "device": {
+             "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+             "ip": "123.145.167.189"
+           },
+           "at": 1,
+           "cur": [
+             "USD"
+           ]
+         }
+       },
+       "mockResponse": {
+         "status": 200,
+         "headers": {},
+         "body": {
+           "id": "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a",
+           "seatbid": [
+             {
+               "bid": [
+                 {
+                  "adid": "52a12b5955314b7194a4c9ff",
+                  "adm": "<iframe src=\"http://ads.com/render/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}\" width=\"728\" height=\"90\" frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\" topmargin=\"0\" leftmargin=\"0\"></iframe>",
+                  "adomain": ["ads.com"],
+                  "cid": "52a12b5955314b7194a4c9ff",
+                  "crid": "52a12b5955314b7194a4c9ff_1386294105",
+                  "dealid": "DX-1985-010A",
+                  "id": "24195efda36066ee21f967bc1de14c82db841f07",
+                  "impid": "121-dt1",
+                  "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}",
+                  "price": 1.028428,
+                  "attr": []
+                 }
+               ],
+               "seat": "42"
+             },
+             {
+               "bid": [
+                 {
+                  "adid": "527c9fdd55314ba06815f25e",
+                  "adm": "<iframe src=\"http://ads.com/render/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}\" width=\"300\" height=\"250\" frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\" topmargin=\"0\" leftmargin=\"0\"></iframe>",
+                  "adomain": ["ads.com"],
+                  "cid": "527c9fdd55314ba06815f25e",
+                  "crid": "527c9fdd55314ba06815f25e_1383899102",
+                  "id": "24195efda36066ee21f967bc1de14c82db841f08",
+                  "impid": "121-dt2",
+                  "nurl": "http://ads.com/win/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}",
+                  "price": 0.04958,
+                  "attr": []
+                 }
+               ],
+               "seat": "772"
+             }
+           ]
+         },
+         "cur": "USD"
+       }
+     }
+   ],
+   "expectedBidResponses": [
+     {
+       "currency": "USD",
+       "bids": [
+         {
+           "type": "banner",
+           "bid": {
+            "adid": "52a12b5955314b7194a4c9ff",
+            "adm": "<iframe src=\"http://ads.com/render/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}\" width=\"728\" height=\"90\" frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\" topmargin=\"0\" leftmargin=\"0\"></iframe>",
+            "adomain": ["ads.com"],
+            "cid": "52a12b5955314b7194a4c9ff",
+            "crid": "52a12b5955314b7194a4c9ff_1386294105",
+            "dealid": "DX-1985-010A",
+            "id": "24195efda36066ee21f967bc1de14c82db841f07",
+            "impid": "121-dt1",
+            "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}",
+            "price": 1.028428
+          }
+         },
+         {
+           "type": "banner",
+           "bid": {
+              "adid": "527c9fdd55314ba06815f25e",
+              "adm": "<iframe src=\"http://ads.com/render/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}\" width=\"300\" height=\"250\" frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\" topmargin=\"0\" leftmargin=\"0\"></iframe>",
+              "adomain": ["ads.com"],
+              "cid": "527c9fdd55314ba06815f25e",
+              "crid": "527c9fdd55314ba06815f25e_1383899102",
+              "id": "24195efda36066ee21f967bc1de14c82db841f08",
+              "impid": "121-dt2",
+              "nurl": "http://ads.com/win/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}",
+              "price": 0.04958
+            }
+         }
+       ]
+     }
+   ]
+ }
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/exemplary/simple-native.json b/adapters/onetag/onetagtest/exemplary/simple-native.json
new file mode 100644
index 00000000..08fef34f
--- /dev/null
+++ b/adapters/onetag/onetagtest/exemplary/simple-native.json
@@ -0,0 +1,121 @@
+{
+    "mockBidRequest": {
+      "id": "some-request-id",
+      "device": {
+        "ua": "test-user-agent",
+        "ip": "123.123.123.123",
+        "language": "en",
+        "dnt": 0
+      },
+      "tmax": 1000,
+      "user": {
+        "buyeruid": "0000-000-000-0000"
+      },
+      "site": {
+        "page": "test.com",
+        "publisher": {
+          "id": "123456789"
+        }
+      },
+      "imp": [
+        {
+          "id": "some-impression-id",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+          "ext": {
+            "bidder": {
+                "pubId": "386276e072",
+                "ext": {
+                  "key1": "value1"
+                }
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+        {
+          "expectedRequest": {
+            "uri": "https://example.com/prebid-server/386276e072",
+            "headers": {},
+            "body": {
+              "id": "some-request-id",
+              "device": {
+                  "ua": "test-user-agent",
+                  "ip": "123.123.123.123",
+                  "language": "en",
+                  "dnt": 0
+              },
+              "imp": [
+                {
+                  "id": "some-impression-id",
+                  "native": {
+                      "ver":"1.1",
+                      "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "ext": {
+                    "key1": "value1"
+                  }
+                }
+              ],
+              "site": {
+                  "page": "test.com",
+                  "publisher": {
+                  "id": "123456789"
+                  }
+              },
+              "user": {
+                  "buyeruid": "0000-000-000-0000"
+              },
+              "tmax": 1000
+              }
+          },
+          "mockResponse": {
+            "status": 200,
+            "body": {
+              "id": "awesome-resp-id",
+              "seatbid": [
+                {
+                  "bid": [
+                    {
+                      "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                      "impid": "some-impression-id",
+                      "price": 3.5,
+                      "adm": "awesome-markup",
+                      "adomain": [
+                        "awesome.com"
+                      ],
+                      "crid": "20"
+                    }
+                  ],
+                  "seat": "onetag"
+                }
+              ],
+              "cur": "USD"
+            }
+          }
+        }
+      ],
+      "expectedBidResponses": [
+        {
+          "currency": "USD",
+          "bids":[
+            {
+              "type": "native",
+              "bid":  {
+                "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                "impid": "some-impression-id",
+                "price": 3.5,
+                "adm": "awesome-markup",
+                "crid": "20",
+                "adomain": [
+                  "awesome.com"
+                ]
+              }
+            }
+          ]
+        }
+      ]
+    }
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/exemplary/simple-video.json b/adapters/onetag/onetagtest/exemplary/simple-video.json
new file mode 100644
index 00000000..ea656a98
--- /dev/null
+++ b/adapters/onetag/onetagtest/exemplary/simple-video.json
@@ -0,0 +1,115 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "device": {
+        "ip": "123.123.123.123",
+        "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      },
+      "app": {
+        "id": "1",
+        "bundle": "com.wls.testwlsapplication"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "instl": 1,
+          "video": {
+            "placement": 3,
+            "mimes": [
+              "video/mp4"
+            ],
+            "protocols": [
+              2,
+              5
+            ],
+            "w": 1024,
+            "h": 576
+          },
+          "ext": {
+            "bidder": {
+              "pubId": "386276e072"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://example.com/prebid-server/386276e072",
+          "headers": {},
+          "body": {
+            "id": "test-request-id",
+            "device": {
+              "ip": "123.123.123.123",
+              "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+            },
+            "app": {
+              "id": "1",
+              "bundle": "com.wls.testwlsapplication"
+            },
+            "imp": [
+              {
+                "id": "test-imp-id",
+                "instl": 1,
+                "video": {
+                  "placement": 3,
+                  "mimes": [
+                    "video/mp4"
+                  ],
+                  "protocols": [
+                    2,
+                    5
+                  ],
+                  "w": 1024,
+                  "h": 576
+                }
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "test-request-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "test_bid_id",
+                    "impid": "test-imp-id",
+                    "price": 0.27543,
+                    "adm": "some-test-ad",
+                    "cid": "test_cid",
+                    "crid": "test_crid",
+                    "dealid": "test_dealid"
+                  }
+                ],
+                "seat": "adman"
+              }
+            ],
+            "cur": "USD"
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test_bid_id",
+              "impid": "test-imp-id",
+              "price": 0.27543,
+              "adm": "some-test-ad",
+              "cid": "test_cid",
+              "crid": "test_crid",
+              "dealid": "test_dealid"
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/params/race/banner.json b/adapters/onetag/onetagtest/params/race/banner.json
new file mode 100644
index 00000000..687438d6
--- /dev/null
+++ b/adapters/onetag/onetagtest/params/race/banner.json
@@ -0,0 +1,4 @@
+{
+    "pubId": "386276e072",
+    "ext": {}
+}
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/params/race/native.json b/adapters/onetag/onetagtest/params/race/native.json
new file mode 100644
index 00000000..687438d6
--- /dev/null
+++ b/adapters/onetag/onetagtest/params/race/native.json
@@ -0,0 +1,4 @@
+{
+    "pubId": "386276e072",
+    "ext": {}
+}
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/params/race/video.json b/adapters/onetag/onetagtest/params/race/video.json
new file mode 100644
index 00000000..687438d6
--- /dev/null
+++ b/adapters/onetag/onetagtest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+    "pubId": "386276e072",
+    "ext": {}
+}
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json b/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json
new file mode 100644
index 00000000..d5ca3b14
--- /dev/null
+++ b/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json
@@ -0,0 +1,65 @@
+{
+    "mockBidRequest": {
+      "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+      "imp": [
+        {
+          "id": "121-dt1",
+          "banner": {
+            "h": 250,
+            "w": 300,
+            "pos": 1
+          },
+          "bidfloor": 0.05,
+          "ext": {
+            "bidder": {
+              "pubId" : "1"
+            }
+          }
+        },
+        {
+          "id": "121-dt2",
+          "banner": {
+            "h": 728,
+            "w": 90,
+            "pos": 0
+          },
+          "bidfloor": 0.12,
+          "ext": {
+            "bidder": {
+              "pubId" : ""
+            }
+          }
+        }
+      ],
+      "site": {
+        "id": "15047",
+        "domain": "dailymotion.com",
+        "cat": ["IAB1"],
+        "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+        "publisher": {
+          "id": "8796",
+          "name": "dailymotion",
+          "cat": ["IAB3-1"],
+          "domain": "dailymotion.com"
+        }
+      },
+      "user": {
+        "id": "518c3da3717203f34019b038"
+      },
+      "device": {
+        "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+        "ip": "123.145.167.189"
+      },
+      "at": 1,
+      "cur": [
+        "USD"
+      ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "The publisher ID must not be empty",
+            "comparison": "literal"
+        }
+    ]
+}
+ 
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/supplemental/internal-server-error.json b/adapters/onetag/onetagtest/supplemental/internal-server-error.json
new file mode 100644
index 00000000..4fc06959
--- /dev/null
+++ b/adapters/onetag/onetagtest/supplemental/internal-server-error.json
@@ -0,0 +1,81 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "device": {
+        "ip": "123.123.123.123",
+        "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+      },
+      "app": {
+        "id": "1",
+        "bundle": "com.wls.testwlsapplication"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "instl": 1,
+          "video": {
+            "placement": 3,
+            "mimes": [
+              "video/mp4"
+            ],
+            "protocols": [
+              2,
+              5
+            ],
+            "w": 1024,
+            "h": 576
+          },
+          "ext": {
+            "bidder": {
+              "pubId": "386276e072"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://example.com/prebid-server/386276e072",
+          "headers": {},
+          "body": {
+            "id": "test-request-id",
+            "device": {
+              "ip": "123.123.123.123",
+              "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+            },
+            "app": {
+              "id": "1",
+              "bundle": "com.wls.testwlsapplication"
+            },
+            "imp": [
+              {
+                "id": "test-imp-id",
+                "instl": 1,
+                "video": {
+                  "placement": 3,
+                  "mimes": [
+                    "video/mp4"
+                  ],
+                  "protocols": [
+                    2,
+                    5
+                  ],
+                  "w": 1024,
+                  "h": 576
+                }
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 500,
+          "body": {}
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": [{
+        "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.",
+        "comparison": "literal"
+    }]
+  }
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/supplemental/required-publisher-id.json b/adapters/onetag/onetagtest/supplemental/required-publisher-id.json
new file mode 100644
index 00000000..6e8ccf0d
--- /dev/null
+++ b/adapters/onetag/onetagtest/supplemental/required-publisher-id.json
@@ -0,0 +1,66 @@
+{
+    "mockBidRequest": {
+      "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+      "imp": [
+        {
+          "id": "121-dt1",
+          "banner": {
+            "h": 250,
+            "w": 300,
+            "pos": 1
+          },
+          "bidfloor": 0.05,
+          "ext": {
+             "bidder": {}
+           }
+         },
+        {
+          "id": "121-dt2",
+          "banner": {
+            "h": 728,
+            "w": 90,
+            "pos": 0
+          },
+          "bidfloor": 0.12,
+          "ext": {
+             "bidder": {
+               "ext": {
+                 "key1": "value1",
+                 "key2": "value2"
+               }
+             }
+           }
+        }
+      ],
+      "site": {
+        "id": "15047",
+        "domain": "dailymotion.com",
+        "cat": ["IAB1"],
+        "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+        "publisher": {
+          "id": "8796",
+          "name": "dailymotion",
+          "cat": ["IAB3-1"],
+          "domain": "dailymotion.com"
+        }
+      },
+      "user": {
+        "id": "518c3da3717203f34019b038"
+      },
+      "device": {
+        "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+        "ip": "123.145.167.189"
+      },
+      "at": 1,
+      "cur": [
+        "USD"
+      ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "The publisher ID must not be empty",
+            "comparison": "literal"
+        }
+    ]
+}
+ 
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json b/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json
new file mode 100644
index 00000000..fe2e3914
--- /dev/null
+++ b/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json
@@ -0,0 +1,69 @@
+{
+    "mockBidRequest": {
+      "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+      "imp": [
+        {
+          "id": "121-dt1",
+          "banner": {
+            "h": 250,
+            "w": 300,
+            "pos": 1
+          },
+          "bidfloor": 0.05,
+          "ext": {
+             "bidder": {
+                "pubId": "386276e072"
+             }
+           }
+         },
+        {
+          "id": "121-dt2",
+          "banner": {
+            "h": 728,
+            "w": 90,
+            "pos": 0
+          },
+          "bidfloor": 0.12,
+          "ext": {
+             "bidder": {
+                "pubId": "386276e072a",
+                "ext": {
+                 "key1": "value1",
+                 "key2": "value2"
+               }
+             }
+           }
+        }
+      ],
+      "site": {
+        "id": "15047",
+        "domain": "dailymotion.com",
+        "cat": ["IAB1"],
+        "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+        "publisher": {
+          "id": "8796",
+          "name": "dailymotion",
+          "cat": ["IAB3-1"],
+          "domain": "dailymotion.com"
+        }
+      },
+      "user": {
+        "id": "518c3da3717203f34019b038"
+      },
+      "device": {
+        "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+        "ip": "123.145.167.189"
+      },
+      "at": 1,
+      "cur": [
+        "USD"
+      ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "There must be only one publisher ID",
+            "comparison": "literal"
+        }
+    ]
+}
+ 
\ No newline at end of file
diff --git a/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json b/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json
new file mode 100644
index 00000000..bfdfcc5f
--- /dev/null
+++ b/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json
@@ -0,0 +1,121 @@
+{
+    "mockBidRequest": {
+      "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+      "imp": [
+        {
+          "id": "121-dt1",
+          "banner": {
+            "h": 250,
+            "w": 300,
+            "pos": 1
+          },
+          "bidfloor": 0.05,
+          "ext": {
+             "bidder": {
+               "pubId": "386276e072"
+             }
+           }
+         }
+        ],
+        "site": {
+          "id": "15047",
+          "domain": "dailymotion.com",
+          "cat": ["IAB1"],
+          "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+          "publisher": {
+            "id": "8796",
+            "name": "dailymotion",
+            "cat": ["IAB3-1"],
+            "domain": "dailymotion.com"
+          }
+        },
+        "user": {
+          "id": "518c3da3717203f34019b038"
+        },
+        "device": {
+          "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+          "ip": "123.145.167.189"
+        },
+        "at": 1,
+        "cur": [
+          "USD"
+        ]
+      },
+      "httpCalls": [
+        {
+          "expectedRequest": {
+            "uri": "https://example.com/prebid-server/386276e072",
+            "headers": {},
+            "body": {
+              "id": "8652a8680db33faabbf3fa76150f35df50a67060",
+              "imp": [
+                {
+                  "id": "121-dt1",
+                  "banner": {
+                    "h": 250,
+                    "w": 300,
+                    "pos": 1
+                  },
+                  "bidfloor": 0.05
+                }
+            ],
+            "site": {
+              "id": "15047",
+              "domain": "dailymotion.com",
+              "cat": ["IAB1"],
+              "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms",
+              "publisher": {
+                "id": "8796",
+                "name": "dailymotion",
+                "cat": ["IAB3-1"],
+                "domain": "dailymotion.com"
+              }
+            },
+            "user": {
+              "id": "518c3da3717203f34019b038"
+            },
+            "device": {
+              "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)",
+              "ip": "123.145.167.189"
+            },
+            "at": 1,
+            "cur": [
+              "USD"
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "headers": {},
+          "body": {
+            "id": "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                   "adid": "52a12b5955314b7194a4c9ff",
+                   "adm": "<iframe src=\"http://ads.com/render/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}\" width=\"728\" height=\"90\" frameborder=\"0\" scrolling=\"no\" marginheight=\"0\" marginwidth=\"0\" topmargin=\"0\" leftmargin=\"0\"></iframe>",
+                   "adomain": ["ads.com"],
+                   "cid": "52a12b5955314b7194a4c9ff",
+                   "crid": "52a12b5955314b7194a4c9ff_1386294105",
+                   "dealid": "DX-1985-010A",
+                   "id": "24195efda36066ee21f967bc1de14c82db841f07",
+                   "impid": "1",
+                   "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}",
+                   "price": 1.028428,
+                   "attr": []
+                  }
+                ],
+                "seat": "42"
+              }
+            ]
+        },
+        "cur": "USD"
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [{
+    "value": "The impression with ID 1 is not present into the request",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/onetag/params_test.go b/adapters/onetag/params_test.go
new file mode 100644
index 00000000..4c7326ac
--- /dev/null
+++ b/adapters/onetag/params_test.go
@@ -0,0 +1,54 @@
+package onetag
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderOneTag, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderOneTag, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{	"pubId": "386276e072",
+		"ext": {
+			"key1": "value1",
+			"key2": "value2"
+		}
+	}`,
+	`{"pubId": "386276e072"}`,
+}
+
+var invalidParams = []string{
+	`{"ext": {
+		"key1": "value1",
+		"key2": "value2"
+	}`,
+	`{}`,
+	`{"pubId": ""}`,
+	`{"pubId": 123}`,
+}
diff --git a/adapters/onetag/usersync_test.go b/adapters/onetag/usersync_test.go
new file mode 100644
index 00000000..31892e6d
--- /dev/null
+++ b/adapters/onetag/usersync_test.go
@@ -0,0 +1,24 @@
+package onetag
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestOneTagSyncer(t *testing.T) {
+	syncURL := "https://onetag-sys.com/usync/?redir="
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+
+	syncer := NewSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "https://onetag-sys.com/usync/?redir=", syncInfo.URL)
+	assert.Equal(t, "iframe", syncInfo.Type)
+	assert.EqualValues(t, 241, syncer.GDPRVendorID())
+}
diff --git a/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json b/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json
new file mode 100644
index 00000000..6a344ee0
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json
@@ -0,0 +1,159 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adSlot": "AdTag_Div1@300x250",
+            "publisherId": "  999  ",
+            "keywords": [
+              {
+                "key": "pmZoneID",
+                "value": [
+                  "Zone1",
+                  "Zone2"
+                ]
+              },
+              {
+                "key": "preference",
+                "value": [
+                  "sports",
+                  "movies"
+                ]
+              }
+            ],
+            "wrapper": {
+              "version": 1,
+              "profile": 5123
+            }
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+    },
+    "site": {
+      "id": "siteID",
+      "publisher": {
+        "id": "1234"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "AdTag_Div1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "h": 250,
+                "w": 300
+              },
+              "ext": {
+                "pmZoneID": "Zone1,Zone2",
+                "preference": "sports,movies"
+              }
+            }
+          ],
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+          },
+          "site": {
+            "id": "siteID",
+            "publisher": {
+              "id": "999"
+            }
+          },
+          "ext": {
+            "wrapper": {
+              "profile": 5123,
+              "version": 1
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [
+                {
+                  "id": "7706636740145184841",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": [
+                    "pubmatic.com"
+                  ],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid": "test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1
+                  }
+                }
+              ]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adid": "29681110",
+            "adm": "some-test-ad",
+            "adomain": [
+              "pubmatic.com"
+            ],
+            "crid": "29681110",
+            "w": 300,
+            "h": 250,
+            "dealid": "test deal",
+            "ext": {
+              "dspid": 6,
+              "deal_channel": 1
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go
index 5346b82b..18719086 100644
--- a/adapters/tappx/tappx_test.go
+++ b/adapters/tappx/tappx_test.go
@@ -12,7 +12,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{
-		Endpoint: "https://{{.Host}}"})
+		Endpoint: "http://{{.Host}}"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -30,7 +30,7 @@ func TestEndpointTemplateMalformed(t *testing.T) {
 
 func TestTsValue(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{
-		Endpoint: "https://{{.Host}}"})
+		Endpoint: "http://{{.Host}}"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) {
 
 	url, err := bidderTappx.buildEndpointURL(&tappxExt, test)
 
-	match, err := regexp.MatchString(`https://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url)
+	match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url)
 	if err != nil {
 		t.Errorf("Error while running regex validation: %s", err.Error())
 		return
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
index 3a365db6..4f5b792f 100644
--- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
@@ -12,8 +12,8 @@
         "ext": {
          "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
@@ -33,7 +33,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
         "body": {
           "id": "0000000000001",
           "test": 1,
@@ -47,8 +47,8 @@
               "ext": {
                 "bidder": {
                   "tappxkey": "pub-12345-android-9876",
-                  "endpoint": "PREBIDTEMPLATE",
-                 "host": "test.tappx.com"
+                  "endpoint": "ZZ123456PS",
+                 "host": "testing.ssp.tappx.com/rtb/v2/"
                 }
               }
             }
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json
new file mode 100644
index 00000000..ef61b0e2
--- /dev/null
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json
@@ -0,0 +1,124 @@
+{
+  "mockBidRequest": {
+    "id": "0000000000001",
+    "test": 1,
+    "imp": [
+      {
+        "id": "adunit-1",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+         "bidder": {
+            "tappxkey": "pub-12345-site-9876",
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "102855",
+      "cat": [ "IAB3-1" ],
+      "domain": "www.tappx.com",
+      "page": "https://www.tappx.com/example.html ",
+      "publisher": {
+        "id": "8953", "name": "publisher.com",
+        "cat": [ "IAB3-1" ],
+        "domain": "publisher.com"
+      }
+    },
+    "user": {
+      "buyeruid": "A-38327932832"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1",
+        "body": {
+          "id": "0000000000001",
+          "test": 1,
+          "imp": [
+            {
+              "id": "adunit-1",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "tappxkey": "pub-12345-site-9876",
+                  "endpoint": "ZZ123456PS",
+                 "host": "testing.ssp.tappx.com/rtb/v2/"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "102855",
+            "cat": [ "IAB3-1" ],
+            "domain": "www.tappx.com",
+            "page": "https://www.tappx.com/example.html ",
+            "publisher": {
+              "id": "8953", "name": "publisher.com",
+              "cat": [ "IAB3-1" ],
+              "domain": "publisher.com"
+            }
+          },
+          "user": {
+            "buyeruid": "A-38327932832"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4",
+          "seatbid": [{
+              "bid": [{
+                  "id": "wehM-93KGr0_0_0",
+                  "impid": "adunit-1",
+                  "price": 0.5,
+                  "cid": "3706",
+                  "crid": "19005",
+                  "adid": "19005",
+                  "adm": "<!-- admarkup -->",
+                  "cat": ["IAB2"],
+                  "adomain": ["test.com"],
+                  "h": 250,
+                  "w": 300
+              }]
+          }],
+          "bidid": "wehM-93KGr0"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "wehM-93KGr0_0_0",
+            "impid": "adunit-1",
+            "price": 0.5,
+            "adm": "<!-- admarkup -->",
+            "adid": "19005",
+            "adomain": ["test.com"],
+            "cid": "3706",
+            "crid": "19005",
+            "w": 300,
+            "h": 250,
+            "cat": ["IAB2"]
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json
index 49cb3c7e..7a469b07 100644
--- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json
+++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json
@@ -15,8 +15,8 @@
         "ext": {
           "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "VZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
@@ -35,7 +35,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
         "body": {
           "id": "0000000000001",
           "test": 1,
@@ -51,8 +51,8 @@
               },
               "ext": {
                 "bidder": {
-                "endpoint": "PREBIDTEMPLATE",
-                "host": "test.tappx.com",
+                "endpoint": "VZ123456PS",
+                "host": "testing.ssp.tappx.com/rtb/v2/",
                 "tappxkey": "pub-12345-android-9876"
                 }
               }
diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json
new file mode 100644
index 00000000..9ee2f8f4
--- /dev/null
+++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json
@@ -0,0 +1,136 @@
+{
+  "mockBidRequest": {
+    "id": "0000000000001",
+    "test": 1,
+    "imp": [
+      {
+        "id": "video-adunit-2",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "bidder": {
+            "tappxkey": "pub-12345-site-9876",
+            "endpoint": "VZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "102855",
+      "cat": [ "IAB3-1" ],
+      "domain": "www.tappx.com",
+      "page": "https://www.tappx.com/example.html ",
+      "publisher": {
+        "id": "8953", "name": "publisher.com",
+        "cat": [ "IAB3-1" ],
+        "domain": "publisher.com"
+      }
+    },
+    "user": {
+      "buyeruid": "A-38327932832"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1",
+        "body": {
+          "id": "0000000000001",
+          "test": 1,
+          "imp": [
+            {
+              "id": "video-adunit-2",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "w": 640,
+                "h": 480
+              },
+              "ext": {
+                "bidder": {
+                "endpoint": "VZ123456PS",
+                "host": "testing.ssp.tappx.com/rtb/v2/",
+                "tappxkey": "pub-12345-site-9876"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "102855",
+            "cat": [ "IAB3-1" ],
+            "domain": "www.tappx.com",
+            "page": "https://www.tappx.com/example.html ",
+            "publisher": {
+              "id": "8953", "name": "publisher.com",
+              "cat": [ "IAB3-1" ],
+              "domain": "publisher.com"
+            }
+          },
+          "user": {
+            "buyeruid": "A-38327932832"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "bid02",
+                  "impid": "video-adunit-2",
+                  "price": 2.25,
+                  "cid": "1001",
+                  "crid": "2002",
+                  "adid": "2002",
+                  "adm": "<!-- vast wrapper -->",
+                  "cat": [
+                    "IAB2"
+                  ],
+                  "adomain": [
+                    "video-example.com"
+                  ]
+                }
+              ]
+            }
+          ],
+          "bidid": "wehM-93KGr0"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+          "currency": "USD",
+          "bids": [
+            {
+              "bid": {
+                "id": "bid02",
+                "impid": "video-adunit-2",
+                "price": 2.25,
+                "adm": "<!-- vast wrapper -->",
+                "adomain": [
+                  "video-example.com"
+                ],
+                "cid": "1001",
+                "adid": "2002",
+                "crid": "2002",
+                "cat": [
+                  "IAB2"
+                ]
+              },
+              "type": "video"
+            }
+          ]
+        }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/tappx/tappxtest/params/race/banner.json b/adapters/tappx/tappxtest/params/race/banner.json
index 4c2ec640..9264443a 100644
--- a/adapters/tappx/tappxtest/params/race/banner.json
+++ b/adapters/tappx/tappxtest/params/race/banner.json
@@ -1,5 +1,5 @@
 {
     "tappxkey": "pub-12345-android-9876",
-    "endpoint": "PREBIDTEMPLATE",
-    "host": "test.tappx.com"
+    "endpoint": "ZZ123456PS",
+    "host": "testing.ssp.tappx.com/rtb/v2/"
 }
\ No newline at end of file
diff --git a/adapters/tappx/tappxtest/params/race/video.json b/adapters/tappx/tappxtest/params/race/video.json
index 4c2ec640..438543f2 100644
--- a/adapters/tappx/tappxtest/params/race/video.json
+++ b/adapters/tappx/tappxtest/params/race/video.json
@@ -1,5 +1,5 @@
 {
     "tappxkey": "pub-12345-android-9876",
-    "endpoint": "PREBIDTEMPLATE",
-    "host": "test.tappx.com"
+    "endpoint": "VZ123456PS",
+    "host": "testing.ssp.tappx.com/rtb/v2/"
 }
\ No newline at end of file
diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json
index 288837b7..6172da59 100644
--- a/adapters/tappx/tappxtest/supplemental/204status.json
+++ b/adapters/tappx/tappxtest/supplemental/204status.json
@@ -12,8 +12,8 @@
         "ext": {
           "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
         "body": {
           "id": "0000000000001",
           "test": 1,
@@ -44,8 +44,8 @@
               "ext": {
                 "bidder": {
                   "tappxkey": "pub-12345-android-9876",
-                  "endpoint": "PREBIDTEMPLATE",
-                  "host": "test.tappx.com"
+                  "endpoint": "ZZ123456PS",
+                  "host": "testing.ssp.tappx.com/rtb/v2/"
                 }
               }
             }
diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json b/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json
index c82d0e85..12f9c54a 100644
--- a/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json
+++ b/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json
@@ -11,7 +11,7 @@
         "ext": {
           "bidder": {
             "host": "example.ho%st.tappx.com",
-            "endpoint": "PREBIDTEMPLATE",
+            "endpoint": "ZZ123456PS",
             "tappxkey": "pub-12345-android-9876"
           }
         }
@@ -31,7 +31,7 @@
 
   "expectedMakeRequestsErrors": [
     {
-      "value": "Malformed URL: parse (\\\")?https://example\\.ho%st\\.tappx.com(\\\")?: invalid URL escape \\\"%st\\\"",
+      "value": "Malformed URL: parse (\\\")?http://example\\.ho%st\\.tappx.com(\\\")?: invalid URL escape \\\"%st\\\"",
       "comparison": "regex"
     }
   ]
diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json b/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json
index 9a49c8b0..85370b4b 100644
--- a/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json
+++ b/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json
@@ -11,7 +11,7 @@
         "ext": {
           "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "host": "test.tappx.com"
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json b/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json
index ffe0f14f..df650dde 100644
--- a/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json
+++ b/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json
@@ -10,7 +10,7 @@
         },
         "ext": {
           "bidder": {
-            "endpoint": "PREBIDTEMPLATE",
+            "endpoint": "ZZ123456PS",
             "tappxkey": "pub-12345-android-9876"
           }
         }
diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json b/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json
index 2bc147ec..4dba6d3b 100644
--- a/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json
+++ b/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json
@@ -10,8 +10,8 @@
         },
         "ext": {
           "bidder": {
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json
index 61e96a44..48c1b0fb 100644
--- a/adapters/tappx/tappxtest/supplemental/bidfloor.json
+++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json
@@ -12,8 +12,8 @@
         "ext": {
          "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com",
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/",
             "bidfloor": 1.5
           }
         }
@@ -34,7 +34,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
         "body": {
           "id": "0000000000001",
           "test": 1,
@@ -49,8 +49,8 @@
               "ext": {
                 "bidder": {
                   "tappxkey": "pub-12345-android-9876",
-                  "endpoint": "PREBIDTEMPLATE",
-                 "host": "test.tappx.com",
+                  "endpoint": "ZZ123456PS",
+                 "host": "testing.ssp.tappx.com/rtb/v2/",
                  "bidfloor": 1.5
                 }
               }
diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json
index c2db2e1e..04569504 100644
--- a/adapters/tappx/tappxtest/supplemental/http-err-status.json
+++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json
@@ -12,8 +12,8 @@
         "ext": {
           "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
         "body": {
           "id": "0000000000001",
           "test": 1,
@@ -44,8 +44,8 @@
               "ext": {
                 "bidder": {
                   "tappxkey": "pub-12345-android-9876",
-                  "endpoint": "PREBIDTEMPLATE",
-                  "host": "test.tappx.com"
+                  "endpoint": "ZZ123456PS",
+                  "host": "testing.ssp.tappx.com/rtb/v2/"
                 }
               }
             }
diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json
index b09ee26b..0cb5fd4a 100644
--- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json
+++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json
@@ -12,8 +12,8 @@
         "ext": {
           "bidder": {
             "tappxkey": "pub-12345-android-9876",
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
         "body": {
           "id": "0000000000001",
           "test": 1,
@@ -44,8 +44,8 @@
               "ext": {
                 "bidder": {
                   "tappxkey": "pub-12345-android-9876",
-                  "endpoint": "PREBIDTEMPLATE",
-                  "host": "test.tappx.com"
+                  "endpoint": "ZZ123456PS",
+                  "host": "testing.ssp.tappx.com/rtb/v2/"
                 }
               }
             }
diff --git a/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json b/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json
index 1cbd3eef..1404204e 100644
--- a/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json
+++ b/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json
@@ -11,8 +11,8 @@
         "ext": {
           "bidder": {
             "tappxkey": 1,
-            "endpoint": "PREBIDTEMPLATE",
-            "host": "test.tappx.com"
+            "endpoint": "ZZ123456PS",
+            "host": "testing.ssp.tappx.com/rtb/v2/"
           }
         }
       }
diff --git a/adapters/tappx/usersync_test.go b/adapters/tappx/usersync_test.go
new file mode 100644
index 00000000..65919b9f
--- /dev/null
+++ b/adapters/tappx/usersync_test.go
@@ -0,0 +1,35 @@
+package tappx
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/ccpa"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTappxSyncer(t *testing.T) {
+	syncURL := "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D"
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+
+	syncer := NewTappxSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal:  "1",
+			Consent: "A",
+		},
+		CCPA: ccpa.Policy{
+			Consent: "1YNN",
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin=1&gdpr_consent=A&us_privacy=1YNN&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%7B%7BTPPXUID%7D%7D", syncInfo.URL)
+	assert.Equal(t, "iframe", syncInfo.Type)
+	assert.EqualValues(t, 628, syncer.GDPRVendorID())
+	assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go
index db26a84b..ed9a526d 100644
--- a/endpoints/auction_test.go
+++ b/endpoints/auction_test.go
@@ -22,6 +22,8 @@ import (
 	gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr"
 	"github.com/prebid/prebid-server/usersync/usersyncers"
 	"github.com/spf13/viper"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestSortBidsAndAddKeywordsForMobile(t *testing.T) {
@@ -376,32 +378,64 @@ func TestCacheVideoOnly(t *testing.T) {
 }
 
 func TestShouldUsersync(t *testing.T) {
-	doTest := func(gdprApplies string, consent string, allowBidderSync bool, allowHostCookies bool, expectAllow bool) {
-		t.Helper()
+	tests := []struct {
+		description      string
+		signal           string
+		allowHostCookies bool
+		allowBidderSync  bool
+		wantAllow        bool
+	}{
+		{
+			description:      "Don't sync - GDPR on, host cookies disallows and bidder sync disallows",
+			signal:           "1",
+			allowHostCookies: false,
+			allowBidderSync:  false,
+			wantAllow:        false,
+		},
+		{
+			description:      "Don't sync - GDPR on, host cookies disallows and bidder sync allows",
+			signal:           "1",
+			allowHostCookies: false,
+			allowBidderSync:  true,
+			wantAllow:        false,
+		},
+		{
+			description:      "Don't sync - GDPR on, host cookies allows and bidder sync disallows",
+			signal:           "1",
+			allowHostCookies: true,
+			allowBidderSync:  false,
+			wantAllow:        false,
+		},
+		{
+			description:      "Sync - GDPR on, host cookies allows and bidder sync allows",
+			signal:           "1",
+			allowHostCookies: true,
+			allowBidderSync:  true,
+			wantAllow:        true,
+		},
+		{
+			description:      "Don't sync - invalid GDPR signal, host cookies disallows and bidder sync disallows",
+			signal:           "2",
+			allowHostCookies: false,
+			allowBidderSync:  false,
+			wantAllow:        false,
+		},
+	}
+
+	for _, tt := range tests {
 		deps := auction{
-			cfg:     nil,
-			syncers: nil,
 			gdprPerms: &auctionMockPermissions{
-				allowBidderSync:  allowBidderSync,
-				allowHostCookies: allowHostCookies,
+				allowBidderSync:  tt.allowBidderSync,
+				allowHostCookies: tt.allowHostCookies,
 			},
-			metricsEngine: nil,
 		}
 		gdprPrivacyPolicy := gdprPolicy.Policy{
-			Signal:  gdprApplies,
-			Consent: consent,
+			Signal: tt.signal,
 		}
 
-		allowSyncs := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy)
-		if allowSyncs != expectAllow {
-			t.Errorf("Expected syncs: %t, allowed syncs: %t", expectAllow, allowSyncs)
-		}
+		allow := deps.shouldUsersync(context.Background(), openrtb_ext.BidderAdform, gdprPrivacyPolicy)
+		assert.Equal(t, tt.wantAllow, allow, tt.description)
 	}
-	doTest("0", "", false, false, true)
-	doTest("1", "", true, true, false)
-	doTest("1", "a", true, false, false)
-	doTest("1", "a", false, true, false)
-	doTest("1", "a", true, true, true)
 }
 
 type auctionMockPermissions struct {
@@ -412,11 +446,11 @@ type auctionMockPermissions struct {
 	allowID          bool
 }
 
-func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) {
 	return m.allowHostCookies, nil
 }
 
-func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) {
 	return m.allowBidderSync, nil
 }
 
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index c790fcd9..a6352387 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -245,11 +245,11 @@ type gdprPerms struct {
 	allowedBidders map[openrtb_ext.BidderName]usersync.Usersyncer
 }
 
-func (g *gdprPerms) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (g *gdprPerms) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) {
 	return g.allowHost, nil
 }
 
-func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) {
 	_, ok := g.allowedBidders[bidder]
 	return ok, nil
 }
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index 60285958..fc98608e 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -101,12 +101,13 @@ func TestSetUIDEndpoint(t *testing.T) {
 			description:           "Add the uid for the requested bidder to the list of existing syncs",
 		},
 		{
-			uri:                  "/setuid?bidder=pubmatic&uid=123&gdpr=0",
-			validFamilyNames:     []string{"pubmatic"},
-			existingSyncs:        nil,
-			expectedSyncs:        map[string]string{"pubmatic": "123"},
-			expectedResponseCode: http.StatusOK,
-			description:          "Don't care about GDPR consent if GDPR is set to 0",
+			uri:                   "/setuid?bidder=pubmatic&uid=123&gdpr=0",
+			validFamilyNames:      []string{"pubmatic"},
+			existingSyncs:         nil,
+			gdprAllowsHostCookies: true,
+			expectedSyncs:         map[string]string{"pubmatic": "123"},
+			expectedResponseCode:  http.StatusOK,
+			description:           "Don't care about GDPR consent if GDPR is set to 0",
 		},
 		{
 			uri:                  "/setuid?bidder=pubmatic&uid=123",
@@ -426,7 +427,7 @@ type mockPermsSetUID struct {
 	allowPI   bool
 }
 
-func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) {
 	var err error
 	if g.errorHost {
 		err = errors.New("something went wrong")
@@ -434,7 +435,7 @@ func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, consent string
 	return g.allowHost, err
 }
 
-func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal gdpr.Signal, consent string) (bool, error) {
 	return false, nil
 }
 
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index e679397b..0407b3c5 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -24,11 +24,11 @@ type permissionsMock struct {
 	personalInfoAllowedError error
 }
 
-func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, gdpr gdpr.Signal, consent string) (bool, error) {
 	return true, nil
 }
 
-func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) {
+func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdpr gdpr.Signal, consent string) (bool, error) {
 	return true, nil
 }
 
diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go
index 902bf14e..5048cf11 100644
--- a/gdpr/gdpr_test.go
+++ b/gdpr/gdpr_test.go
@@ -48,3 +48,55 @@ func TestNewPermissions(t *testing.T) {
 		assert.IsType(t, tt.wantType, perms, tt.description)
 	}
 }
+
+func TestSignalParse(t *testing.T) {
+	tests := []struct {
+		description string
+		rawSignal   string
+		wantSignal  Signal
+		wantError   bool
+	}{
+		{
+			description: "valid raw signal is 0",
+			rawSignal:   "0",
+			wantSignal:  SignalNo,
+			wantError:   false,
+		},
+		{
+			description: "Valid signal - raw signal is 1",
+			rawSignal:   "1",
+			wantSignal:  SignalYes,
+			wantError:   false,
+		},
+		{
+			description: "Valid signal - raw signal is empty",
+			rawSignal:   "",
+			wantSignal:  SignalAmbiguous,
+			wantError:   false,
+		},
+		{
+			description: "Invalid signal - raw signal is -1",
+			rawSignal:   "-1",
+			wantSignal:  SignalAmbiguous,
+			wantError:   true,
+		},
+		{
+			description: "Invalid signal - raw signal is abc",
+			rawSignal:   "abc",
+			wantSignal:  SignalAmbiguous,
+			wantError:   true,
+		},
+	}
+
+	for _, tt := range tests {
+		signal, err := SignalParse(tt.rawSignal)
+
+		assert.Equal(t, tt.wantSignal, signal, tt.description)
+
+		if tt.wantError {
+			assert.NotNil(t, err, tt.description)
+		} else {
+			assert.Nil(t, err, tt.description)
+		}
+	}
+}
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index a7d4b26a..45d2ba43 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -15,7 +15,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestNoConsentButAllowByDefault(t *testing.T) {
+func TestDisallowOnEmptyConsent(t *testing.T) {
 	perms := permissionsImpl{
 		cfg: config.GDPR{
 			HostVendorID:        3,
@@ -27,34 +27,27 @@ func TestNoConsentButAllowByDefault(t *testing.T) {
 			tcf2SpecVersion: failedListFetcher,
 		},
 	}
-	allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "")
-	assertBoolsEqual(t, true, allowSync)
-	assertNilErr(t, err)
-	allowSync, err = perms.HostCookiesAllowed(context.Background(), "")
-	assertBoolsEqual(t, true, allowSync)
-	assertNilErr(t, err)
-}
-
-func TestNoConsentAndRejectByDefault(t *testing.T) {
-	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID:        3,
-			UsersyncIfAmbiguous: false,
-		},
-		vendorIDs: nil,
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf1SpecVersion: failedListFetcher,
-			tcf2SpecVersion: failedListFetcher,
-		},
-	}
-	allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, "")
+	allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, "")
 	assertBoolsEqual(t, false, allowSync)
 	assertNilErr(t, err)
-	allowSync, err = perms.HostCookiesAllowed(context.Background(), "")
+	allowSync, err = perms.HostCookiesAllowed(context.Background(), SignalYes, "")
 	assertBoolsEqual(t, false, allowSync)
 	assertNilErr(t, err)
 }
 
+func TestAllowOnSignalNo(t *testing.T) {
+	perms := permissionsImpl{}
+	emptyConsent := ""
+
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalNo, emptyConsent)
+	assert.Equal(t, true, allowSync)
+	assert.Nil(t, err)
+
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalNo, emptyConsent)
+	assert.Equal(t, true, allowSync)
+	assert.Nil(t, err)
+}
+
 func TestAllowedSyncs(t *testing.T) {
 	vendorListData := tcf1MarshalVendorList(tcf1VendorList{
 		VendorListVersion: 1,
@@ -81,11 +74,11 @@ func TestAllowedSyncs(t *testing.T) {
 		},
 	}
 
-	allowSync, err := perms.HostCookiesAllowed(context.Background(), "BON3PCUON3PCUABABBAAABoAAAAAMw")
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, true, allowSync)
 
-	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BON3PCUON3PCUABABBAAABoAAAAAMw")
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, true, allowSync)
 }
@@ -116,11 +109,11 @@ func TestProhibitedPurposes(t *testing.T) {
 		},
 	}
 
-	allowSync, err := perms.HostCookiesAllowed(context.Background(), "BON3PCUON3PCUABABBAAABAAAAAAMw")
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, false, allowSync)
 
-	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BON3PCUON3PCUABABBAAABAAAAAAMw")
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, false, allowSync)
 }
@@ -151,11 +144,11 @@ func TestProhibitedVendors(t *testing.T) {
 		},
 	}
 
-	allowSync, err := perms.HostCookiesAllowed(context.Background(), "BOS2bx5OS2bx5ABABBAAABoAAAAAFA")
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, false, allowSync)
 
-	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA")
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA")
 	assertNilErr(t, err)
 	assertBoolsEqual(t, false, allowSync)
 }
@@ -171,7 +164,7 @@ func TestMalformedConsent(t *testing.T) {
 		},
 	}
 
-	sync, err := perms.HostCookiesAllowed(context.Background(), "BON")
+	sync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON")
 	assertErr(t, err, true)
 	assertBoolsEqual(t, false, sync)
 }
@@ -618,11 +611,11 @@ func TestAllowSyncTCF2(t *testing.T) {
 	}
 
 	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
-	allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "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")
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
 	assert.NoErrorf(t, err, "Error processing BidderSyncAllowed")
 	assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure")
 }
@@ -648,11 +641,11 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) {
 	perms.cfg.HostVendorID = 8
 
 	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8
-	allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "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")
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderRubicon, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
 	assert.NoErrorf(t, err, "Error processing BidderSyncAllowed")
 	assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure")
 }
@@ -677,12 +670,12 @@ func TestProhibitedVendorSyncTCF2(t *testing.T) {
 	perms.cfg.HostVendorID = 10
 
 	// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8
-	allowSync, err := perms.HostCookiesAllowed(context.Background(), "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
 	assert.NoErrorf(t, err, "Error processing HostCookiesAllowed")
 	assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure")
 
 	// Permission disallowed due to consent string not including vendor 10.
-	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
+	allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderOpenx, SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA")
 	assert.NoErrorf(t, err, "Error processing BidderSyncAllowed")
 	assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure")
 }
@@ -750,3 +743,61 @@ func assertStringsEqual(t *testing.T, expected string, actual string) {
 		t.Errorf("Expected %s, got %s", expected, actual)
 	}
 }
+
+func TestNormalizeGDPR(t *testing.T) {
+	tests := []struct {
+		description         string
+		userSyncIfAmbiguous bool
+		giveSignal          Signal
+		wantSignal          Signal
+	}{
+		{
+			description:         "Don't normalize - Signal No and userSyncIfAmbiguous false",
+			userSyncIfAmbiguous: false,
+			giveSignal:          SignalNo,
+			wantSignal:          SignalNo,
+		},
+		{
+			description:         "Don't normalize - Signal No and userSyncIfAmbiguous true",
+			userSyncIfAmbiguous: true,
+			giveSignal:          SignalNo,
+			wantSignal:          SignalNo,
+		},
+		{
+			description:         "Don't normalize - Signal Yes and userSyncIfAmbiguous false",
+			userSyncIfAmbiguous: false,
+			giveSignal:          SignalYes,
+			wantSignal:          SignalYes,
+		},
+		{
+			description:         "Don't normalize - Signal Yes and userSyncIfAmbiguous true",
+			userSyncIfAmbiguous: true,
+			giveSignal:          SignalYes,
+			wantSignal:          SignalYes,
+		},
+		{
+			description:         "Normalize - Signal Ambiguous and userSyncIfAmbiguous false",
+			userSyncIfAmbiguous: false,
+			giveSignal:          SignalAmbiguous,
+			wantSignal:          SignalYes,
+		},
+		{
+			description:         "Normalize - Signal Ambiguous and userSyncIfAmbiguous true",
+			userSyncIfAmbiguous: true,
+			giveSignal:          SignalAmbiguous,
+			wantSignal:          SignalNo,
+		},
+	}
+
+	for _, tt := range tests {
+		perms := permissionsImpl{
+			cfg: config.GDPR{
+				UsersyncIfAmbiguous: tt.userSyncIfAmbiguous,
+			},
+		}
+
+		normalizedSignal := perms.normalizeGDPR(tt.giveSignal)
+
+		assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description)
+	}
+}
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index 77f3f294..27f1bc3b 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -66,67 +66,13 @@ func TestTCF1FetcherInitialLoad(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		runTest(t, test, tcf1SpecVersion, server)
-	}
-}
-
-func TestTCF2FetcherInitialLoad(t *testing.T) {
-	// Loads two vendor lists during initialization by setting the latest vendor list version to 2.
-	// Ensures TCF1 fetch settings have no effect on TCF2.
-
-	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
-		vendorListLatestVersion: 2,
-		vendorLists: map[int]string{
-			1: tcf2VendorList1,
-			2: tcf2VendorList2,
-		},
-	})))
-	defer server.Close()
-
-	testCases := []test{
-		{
-			description: "Fallback - Vendor List 1",
-			setup: testSetup{
-				enableTCF1Fallback: true,
-				vendorListVersion:  1,
-			},
-			expected: vendorList1Expected,
-		},
-		{
-			description: "Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "No Fallback - Vendor List 1",
-			setup: testSetup{
-				enableTCF1Fallback: false,
-				vendorListVersion:  1,
-			},
-			expected: vendorList1Expected,
-		},
-		{
-			description: "No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-	}
-
-	for _, test := range testCases {
-		runTest(t, test, tcf2SpecVersion, server)
+		runTestTCF1(t, test, server)
 	}
 }
 
 func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
 	// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
 	// All other vendor lists will be dynamically loaded.
-	// Ensures TCF1 fetch settings have no effect on TCF2.
 
 	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
 		vendorListLatestVersion: 1,
@@ -137,34 +83,20 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
 	})))
 	defer server.Close()
 
-	testCases := []test{
-		{
-			description: "Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
+	test := test{
+		description: "Dynamic Load - List Exists",
+		setup: testSetup{
+			vendorListVersion: 2,
 		},
+		expected: vendorList2Expected,
 	}
 
-	for _, test := range testCases {
-		runTest(t, test, tcf2SpecVersion, server)
-	}
+	runTestTCF2(t, test, server)
 }
 
 func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
 	// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
 	// All other vendor list load attempts will be done dynamically.
-	// Ensures TCF1 fetch settings have no effect on TCF2.
 
 	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
 		vendorListLatestVersion: 1,
@@ -174,32 +106,17 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
 	})))
 	defer server.Close()
 
-	testCases := []test{
-		{
-			description: "Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
+	test := test{
+		description: "No Fallback - Vendor Doesn't Exist",
+		setup: testSetup{
+			vendorListVersion: 2,
 		},
-		{
-			description: "No Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
+		expected: testExpected{
+			errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
 		},
 	}
 
-	for _, test := range testCases {
-		runTest(t, test, tcf2SpecVersion, server)
-	}
+	runTestTCF2(t, test, server)
 }
 
 func TestTCF2FetcherThrottling(t *testing.T) {
@@ -222,7 +139,7 @@ func TestTCF2FetcherThrottling(t *testing.T) {
 	})))
 	defer server.Close()
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion)
+	fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server))
 
 	// Dynamically Load List 2 Successfully
 	_, errList1 := fetcher(context.Background(), 2)
@@ -243,7 +160,7 @@ func TestTCF2MalformedVendorlist(t *testing.T) {
 	})))
 	defer server.Close()
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion)
+	fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server))
 	_, err := fetcher(context.Background(), 1)
 
 	// Fetching should fail since vendor list could not be unmarshalled.
@@ -254,9 +171,9 @@ func TestTCF2ServerUrlInvalid(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 	server.Close()
 
-	invalidURLGenerator := func(uint16, uint8) string { return " http://invalid-url-has-leading-whitespace" }
+	invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" }
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator, tcf2SpecVersion)
+	fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), invalidURLGenerator)
 	_, err := fetcher(context.Background(), 1)
 
 	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
@@ -266,7 +183,7 @@ func TestTCF2ServerUnavailable(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 	server.Close()
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion)
+	fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server))
 	_, err := fetcher(context.Background(), 1)
 
 	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
@@ -275,38 +192,23 @@ func TestTCF2ServerUnavailable(t *testing.T) {
 func TestVendorListURLMaker(t *testing.T) {
 	testCases := []struct {
 		description       string
-		tcfSpecVersion    uint8
 		vendorListVersion uint16
 		expectedURL       string
 	}{
 		{
-			description:       "TCF1 - Latest",
-			tcfSpecVersion:    1,
-			vendorListVersion: 0, // Forces latest version.
-			expectedURL:       "https://vendor-list.consensu.org/vendorlist.json",
-		},
-		{
-			description:       "TCF1 - Specific",
-			tcfSpecVersion:    1,
-			vendorListVersion: 42,
-			expectedURL:       "https://vendor-list.consensu.org/v-42/vendorlist.json",
-		},
-		{
-			description:       "TCF2 - Latest",
-			tcfSpecVersion:    2,
-			vendorListVersion: 0, // Forces latest version.
+			description:       "Latest",
+			vendorListVersion: 0,
 			expectedURL:       "https://vendor-list.consensu.org/v2/vendor-list.json",
 		},
 		{
-			description:       "TCF2 - Specific",
-			tcfSpecVersion:    2,
+			description:       "Specific",
 			vendorListVersion: 42,
 			expectedURL:       "https://vendor-list.consensu.org/v2/archives/vendor-list-v42.json",
 		},
 	}
 
 	for _, test := range testCases {
-		result := vendorListURLMaker(test.vendorListVersion, test.tcfSpecVersion)
+		result := vendorListURLMaker(test.vendorListVersion)
 		assert.Equal(t, test.expectedURL, result)
 	}
 }
@@ -321,12 +223,6 @@ var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{
 	Vendors:           map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}},
 })
 
-var vendorList1Expected = testExpected{
-	vendorListVersion: 1,
-	vendorID:          12,
-	vendorPurposes:    map[int]bool{1: false, 2: true, 3: false},
-}
-
 var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{
 	VendorListVersion: 2,
 	Vendors:           []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}},
@@ -438,13 +334,31 @@ type testExpected struct {
 	vendorPurposes    map[int]bool
 }
 
-func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Server) {
+func runTestTCF1(t *testing.T, test test, server *httptest.Server) {
 	config := testConfig()
 	if test.setup.enableTCF1Fallback {
 		config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
 	}
 
-	fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server), tcfSpecVersion)
+	fetcher := newVendorListFetcherTCF1(config)
+	vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion)
+
+	if test.expected.errorMessage != "" {
+		assert.EqualError(t, err, test.expected.errorMessage, test.description+":error")
+	} else {
+		assert.NoError(t, err, test.description+":vendorlist")
+		assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid")
+		vendor := vendorList.Vendor(test.expected.vendorID)
+		for id, expected := range test.expected.vendorPurposes {
+			result := vendor.Purpose(consentconstants.Purpose(id))
+			assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id)
+		}
+	}
+}
+
+func runTestTCF2(t *testing.T, test test, server *httptest.Server) {
+	config := testConfig()
+	fetcher := newVendorListFetcherTCF2(context.Background(), config, server.Client(), testURLMaker(server))
 	vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion)
 
 	if test.expected.errorMessage != "" {
@@ -460,9 +374,9 @@ func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Ser
 	}
 }
 
-func testURLMaker(server *httptest.Server) func(uint16, uint8) string {
+func testURLMaker(server *httptest.Server) func(uint16) string {
 	url := server.URL
-	return func(vendorListVersion uint16, tcfSpecVersion uint8) string {
+	return func(vendorListVersion uint16) string {
 		return url + "?version=" + strconv.Itoa(int(vendorListVersion))
 	}
 }
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 73f0e886..66a0dfc2 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -64,6 +64,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderNanoInteractive):  syncConfig,
 			string(openrtb_ext.BidderNinthDecimal):     syncConfig,
 			string(openrtb_ext.BidderNoBid):            syncConfig,
+			string(openrtb_ext.BidderOneTag):           syncConfig,
 			string(openrtb_ext.BidderOpenx):            syncConfig,
 			string(openrtb_ext.BidderPubmatic):         syncConfig,
 			string(openrtb_ext.BidderPulsepoint):       syncConfig,
@@ -78,6 +79,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderSonobi):           syncConfig,
 			string(openrtb_ext.BidderSovrn):            syncConfig,
 			string(openrtb_ext.BidderSynacormedia):     syncConfig,
+			string(openrtb_ext.BidderTappx):            syncConfig,
 			string(openrtb_ext.BidderTelaria):          syncConfig,
 			string(openrtb_ext.BidderTriplelift):       syncConfig,
 			string(openrtb_ext.BidderTripleliftNative): syncConfig,
@@ -112,7 +114,6 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderRevcontent:   true,
 		openrtb_ext.BidderSilverMob:    true,
 		openrtb_ext.BidderSmaato:       true,
-		openrtb_ext.BidderTappx:        true,
 		openrtb_ext.BidderYeahmobi:     true,
 	}