Repository: prebid/prebid-server
Pull request #1674 (2021-01-22 08:39:53)

Tappx User Syncer + Site Update
New pull request all in one.
Unified the next pull request

Tappx - two changes #1668
Update tappx.yaml to accept site #1667
Tappx - two changes #1668
Create usersync.go #1665

SyntaxNode: Thank you. I'm going to close the individual PRs.
prebidtappx: Hi! I'm not sure (I'm the rookie of the company) you had received the corrections?
And... There is something more that I have to do?
Thanks for all!
prebidtappx: > > Hi! I'm not sure (I'm the rookie of the company) you had received the corrections?
> 
> We have. There are 12 open PRs at the moment. We kindly ask for your patience as we review all contributions.
> 
> This PR looks good to me. There are merge conflicts now (it happens) which we need you to resolve with merging in the master branch.

Hi! Sorry again, we just make a merge to our fork tappx-com/prebid-server. ¿This is all? We have to do something more... I don´t now what merge conflicts you need that we resolve.
Thanks for your patience 😅
SyntaxNode: > Hi! Sorry again, we just make a merge to our fork tappx-com/prebid-server. ¿This is all? We have to do something more... I don´t now what merge conflicts you need that we resolve.
> Thanks for your patience 😅

Yes. If you scroll to the bottom of the Github PR, it will show you which conflicts need to be resolved in the "Conflicting Files" section. Performing a git merge against the master branch with manual resolution is what you need to fix those.
prebidtappx: Hi @SyntaxNode !
We are doing a lot of trials to pass de github validator. Is always failing in usersync_test.go.
The test don't said nothing more that the file have issues.
But we don't find more issues... ¿You have some idea that what is happening?

Patch:
diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go
index 5eeeb132..e84df5b8 100644
--- a/adapters/adform/adform.go
+++ b/adapters/adform/adform.go
@@ -44,23 +44,11 @@ type adformRequest struct {
 	adUnits       []*adformAdUnit
 	gdprApplies   string
 	consent       string
-	digitrust     *adformDigitrust
 	currency      string
 	eids          string
 	url           string
 }
 
-type adformDigitrust struct {
-	Id      string                 `json:"id"`
-	Version int                    `json:"version"`
-	Keyv    int                    `json:"keyv"`
-	Privacy adformDigitrustPrivacy `json:"privacy"`
-}
-
-type adformDigitrustPrivacy struct {
-	Optout bool `json:"optout"`
-}
-
 type adformAdUnit struct {
 	MasterTagId json.Number `json:"mid"`
 	PriceType   string      `json:"priceType,omitempty"`
@@ -216,24 +204,6 @@ func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder
 		gdprApplies = ""
 	}
 	consent := request.ParseConsent()
-	var digitrustData *openrtb_ext.ExtUserDigiTrust
-	if request.User != nil {
-		var extUser *openrtb_ext.ExtUser
-		if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
-			digitrustData = extUser.DigiTrust
-		}
-	}
-
-	var digitrust *adformDigitrust = nil
-	if digitrustData != nil {
-		digitrust = new(adformDigitrust)
-		digitrust.Id = digitrustData.ID
-		digitrust.Keyv = digitrustData.KeyV
-		digitrust.Version = 1
-		digitrust.Privacy = adformDigitrustPrivacy{
-			Optout: digitrustData.Pref != 0,
-		}
-	}
 
 	return &adformRequest{
 		adUnits:       adUnits,
@@ -247,7 +217,6 @@ func pbsRequestToAdformRequest(a *AdformAdapter, request *pbs.PBSRequest, bidder
 		tid:           request.Tid,
 		gdprApplies:   gdprApplies,
 		consent:       consent,
-		digitrust:     digitrust,
 		currency:      defaultCurrency,
 	}, nil
 }
@@ -371,18 +340,9 @@ func (r *adformRequest) buildAdformHeaders(a *AdformAdapter) http.Header {
 		header.Set("Referer", r.referer)
 	}
 
-	cookie := make([]string, 0, 2)
 	if r.userId != "" {
-		cookie = append(cookie, fmt.Sprintf("uid=%s", r.userId))
-	}
-	if r.digitrust != nil {
-		if digitrustBytes, err := json.Marshal(r.digitrust); err == nil {
-			digitrust := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(digitrustBytes)
-			// Cookie name and structure are described here: https://github.com/digi-trust/dt-cdn/wiki/Cookies-for-Platforms
-			cookie = append(cookie, fmt.Sprintf("DigiTrust.v1.identity=%s", digitrust))
-		}
+		header.Set("Cookie", fmt.Sprintf("uid=%s;", r.userId))
 	}
-	header.Set("Cookie", strings.Join(cookie, ";"))
 
 	return header
 }
@@ -506,27 +466,14 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro
 
 	eids := ""
 	consent := ""
-	var digitrustData *openrtb_ext.ExtUserDigiTrust
 	if request.User != nil {
 		var extUser openrtb_ext.ExtUser
 		if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
 			consent = extUser.Consent
-			digitrustData = extUser.DigiTrust
 			eids = encodeEids(extUser.Eids)
 		}
 	}
 
-	var digitrust *adformDigitrust = nil
-	if digitrustData != nil {
-		digitrust = new(adformDigitrust)
-		digitrust.Id = digitrustData.ID
-		digitrust.Keyv = digitrustData.KeyV
-		digitrust.Version = 1
-		digitrust.Privacy = adformDigitrustPrivacy{
-			Optout: digitrustData.Pref != 0,
-		}
-	}
-
 	requestCurrency := defaultCurrency
 	if len(request.Cur) != 0 {
 		hasDefaultCurrency := false
@@ -552,7 +499,6 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro
 		tid:           tid,
 		gdprApplies:   gdprApplies,
 		consent:       consent,
-		digitrust:     digitrust,
 		currency:      requestCurrency,
 		eids:          eids,
 		url:           url,
diff --git a/adapters/decenterads/decenterads.go b/adapters/decenterads/decenterads.go
new file mode 100644
index 00000000..5719bf1e
--- /dev/null
+++ b/adapters/decenterads/decenterads.go
@@ -0,0 +1,124 @@
+package decenterads
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"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/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func (a *adapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	impressions := request.Imp
+	result := make([]*adapters.RequestData, 0, len(impressions))
+	errs := make([]error, 0, len(impressions))
+
+	for _, impression := range impressions {
+		var impExt map[string]json.RawMessage
+		if err := json.Unmarshal(impression.Ext, &impExt); err != nil {
+			errs = append(errs, fmt.Errorf("unable to parse bidder parameers: %s", err))
+			continue
+		}
+
+		bidderExt, bidderExtExists := impExt["bidder"]
+		if !bidderExtExists || len(bidderExt) == 0 {
+			errs = append(errs, errors.New("bidder parameters required"))
+			continue
+		}
+
+		impression.Ext = bidderExt
+		request.Imp = []openrtb.Imp{impression}
+		body, err := json.Marshal(request)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		result = append(result, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     a.endpoint,
+			Body:    body,
+			Headers: headers,
+		})
+	}
+
+	request.Imp = impressions
+	return result, errs
+}
+
+func (a *adapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	switch responseData.StatusCode {
+	case http.StatusNoContent:
+		return nil, nil
+	case http.StatusBadRequest:
+		return nil, []error{&errortypes.BadInput{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode),
+		}}
+	case http.StatusOK:
+		break
+	default:
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode),
+		}}
+	}
+
+	var bidResponse openrtb.BidResponse
+	err := json.Unmarshal(responseData.Body, &bidResponse)
+	if err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: err.Error(),
+		}}
+	}
+
+	response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+
+	for _, seatBid := range bidResponse.SeatBid {
+		for i := range seatBid.Bid {
+			response.Bids = append(response.Bids, &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: getMediaTypeForImp(seatBid.Bid[i].ImpID, request.Imp),
+			})
+		}
+	}
+
+	return response, errs
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
+	for _, imp := range imps {
+		if imp.ID == impID {
+			if imp.Banner != nil {
+				return openrtb_ext.BidTypeBanner
+			} else if imp.Video != nil {
+				return openrtb_ext.BidTypeVideo
+			} else if imp.Native != nil {
+				return openrtb_ext.BidTypeNative
+			} else if imp.Audio != nil {
+				return openrtb_ext.BidTypeAudio
+			}
+		}
+	}
+	return openrtb_ext.BidTypeBanner
+}
+
+// Builder builds a new instance of the DecenterAds adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
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 f040040b..755c8fc9
--- a/config/config.go
+++ b/config/config.go
@@ -590,6 +590,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/match/bounce/current?version=1&networkId=72582&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderCpmstar, "https://server.cpmstar.com/usersync.aspx?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcpmstar%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D")
+	// openrtb_ext.BidderDecenterAds doesn't have a good default.
 	// openrtb_ext.BidderDMX doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID")
@@ -628,7 +629,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")
@@ -794,7 +795,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb")
 	v.SetDefault("adapters.33across.partner_id", "")
 	v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}")
-	v.SetDefault("adapters.adform.endpoint", "http://adx.adform.net/adx")
+	v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx")
 	v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
 	v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json")
 	v.SetDefault("adapters.adkernel.endpoint", "http://{{.Host}}/hb?zone={{.ZoneID}}")
@@ -828,6 +829,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25")
 	v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
+	v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb")
 	v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid")
 	v.SetDefault("adapters.dmx.endpoint", "https://dmx-direct.districtm.io/b/v2")
 	v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com")
@@ -877,7 +879,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 8da57d6e..f05a4d81 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -35,6 +35,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
 	"github.com/prebid/prebid-server/adapters/datablocks"
+	"github.com/prebid/prebid-server/adapters/decenterads"
 	"github.com/prebid/prebid-server/adapters/deepintent"
 	"github.com/prebid/prebid-server/adapters/dmx"
 	"github.com/prebid/prebid-server/adapters/emx_digital"
@@ -136,6 +137,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderConversant:       conversant.Builder,
 		openrtb_ext.BidderCpmstar:          cpmstar.Builder,
 		openrtb_ext.BidderDatablocks:       datablocks.Builder,
+		openrtb_ext.BidderDecenterAds:      decenterads.Builder,
 		openrtb_ext.BidderDeepintent:       deepintent.Builder,
 		openrtb_ext.BidderDmx:              dmx.Builder,
 		openrtb_ext.BidderEmxDigital:       emx_digital.Builder,
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index bfc238e1..f24fd6c5 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.
 	//
@@ -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/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index c0839632..576b1d1a 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -74,6 +74,7 @@ const (
 	BidderCpmstar          BidderName = "cpmstar"
 	BidderDatablocks       BidderName = "datablocks"
 	BidderDmx              BidderName = "dmx"
+	BidderDecenterAds      BidderName = "decenterads"
 	BidderDeepintent       BidderName = "deepintent"
 	BidderEmxDigital       BidderName = "emx_digital"
 	BidderEngageBDR        BidderName = "engagebdr"
@@ -173,6 +174,7 @@ func CoreBidderNames() []BidderName {
 		BidderConversant,
 		BidderCpmstar,
 		BidderDatablocks,
+		BidderDecenterAds,
 		BidderDeepintent,
 		BidderDmx,
 		BidderEmxDigital,
diff --git a/openrtb_ext/imp_decenterads.go b/openrtb_ext/imp_decenterads.go
new file mode 100644
index 00000000..101b85c1
--- /dev/null
+++ b/openrtb_ext/imp_decenterads.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpDecenterAds struct {
+	PlacementID string `json:"placementId"`
+}
diff --git a/static/bidder-info/decenterads.yaml b/static/bidder-info/decenterads.yaml
new file mode 100644
index 00000000..c9b349cd
--- /dev/null
+++ b/static/bidder-info/decenterads.yaml
@@ -0,0 +1,14 @@
+maintainer:
+  email: "support@decenterads.com"
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
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/decenterads.json b/static/bidder-params/decenterads.json
new file mode 100644
index 00000000..970e9903
--- /dev/null
+++ b/static/bidder-params/decenterads.json
@@ -0,0 +1,18 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "DecenterAds Adapter Params",
+  "description": "A schema which validates params accepted by the DecenterAds adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "An ID which identifies the DecenterAds placement"
+    },
+    "customParams": {
+      "type": "object",
+      "description": "User-defined targeting key-value pairs."
+    }
+  },
+  "required" : [ "placementId" ]
+}
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
old mode 100755
new mode 100644
index c41f7c6c..25b37fa3
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -69,6 +69,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"
@@ -156,6 +157,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/adform/adform_test.go b/adapters/adform/adform_test.go
index 8ce55a12..558453e4 100644
--- a/adapters/adform/adform_test.go
+++ b/adapters/adform/adform_test.go
@@ -27,7 +27,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{
-		Endpoint: "http://adx.adform.net/adx"})
+		Endpoint: "https://adx.adform.net/adx"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -300,7 +300,7 @@ func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer
 
 func TestOpenRTBRequest(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderAdform, config.Adapter{
-		Endpoint: "http://adx.adform.net"})
+		Endpoint: "https://adx.adform.net"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -525,12 +525,6 @@ func getRegs() openrtb.Regs {
 }
 
 func getUserExt() []byte {
-	digitrust := openrtb_ext.ExtUserDigiTrust{
-		ID:   "digitrustId",
-		KeyV: 1,
-		Pref: 0,
-	}
-
 	eids := []openrtb_ext.ExtUserEid{
 		{
 			Source: "test.com",
@@ -556,9 +550,8 @@ func getUserExt() []byte {
 	}
 
 	userExt := openrtb_ext.ExtUser{
-		Eids:      eids,
-		Consent:   "abc",
-		DigiTrust: &digitrust,
+		Eids:    eids,
+		Consent: "abc",
 	}
 	userExtData, err := json.Marshal(userExt)
 	if err == nil {
@@ -630,7 +623,7 @@ func assertAdformServerRequest(testData aBidInfo, r *http.Request, isOpenRtb boo
 	if ok, err := equal(testData.referrer, r.Header.Get("Referer"), "Referer"); !ok {
 		return err
 	}
-	if ok, err := equal(fmt.Sprintf("uid=%s;DigiTrust.v1.identity=eyJpZCI6ImRpZ2l0cnVzdElkIiwidmVyc2lvbiI6MSwia2V5diI6MSwicHJpdmFjeSI6eyJvcHRvdXQiOmZhbHNlfX0", testData.buyerUID), r.Header.Get("Cookie"), "Buyer ID"); !ok {
+	if ok, err := equal(fmt.Sprintf("uid=%s;", testData.buyerUID), r.Header.Get("Cookie"), "Buyer ID"); !ok {
 		return err
 	}
 	return nil
diff --git a/adapters/adform/adformtest/exemplary/multiformat-impression.json b/adapters/adform/adformtest/exemplary/multiformat-impression.json
index efd4aca6..5b3067ab 100644
--- a/adapters/adform/adformtest/exemplary/multiformat-impression.json
+++ b/adapters/adform/adformtest/exemplary/multiformat-impression.json
@@ -35,7 +35,7 @@
     "httpCalls": [
       {
         "expectedRequest": {
-          "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE"
+          "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE&bWlkPTU0MzIxJnJjdXI9VVNE"
         },
         "mockResponse": {
           "status": 200,
diff --git a/adapters/adform/adformtest/exemplary/single-banner-impression.json b/adapters/adform/adformtest/exemplary/single-banner-impression.json
index fd7f3cde..8a5f81c8 100644
--- a/adapters/adform/adformtest/exemplary/single-banner-impression.json
+++ b/adapters/adform/adformtest/exemplary/single-banner-impression.json
@@ -23,7 +23,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE"
+        "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE"
       },
       "mockResponse": {
         "status": 200,
diff --git a/adapters/adform/adformtest/exemplary/single-video-impression.json b/adapters/adform/adformtest/exemplary/single-video-impression.json
index e22977e6..383e091b 100644
--- a/adapters/adform/adformtest/exemplary/single-video-impression.json
+++ b/adapters/adform/adformtest/exemplary/single-video-impression.json
@@ -19,7 +19,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE"
+        "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTU0MzIxJnJjdXI9VVNE"
       },
       "mockResponse": {
         "status": 200,
diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json
index 34b857d5..5f02fe85 100644
--- a/adapters/adform/adformtest/supplemental/user-nil.json
+++ b/adapters/adform/adformtest/supplemental/user-nil.json
@@ -44,7 +44,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://adx.adform.net/adx?CC=1&fd=1&gdpr=1&gdpr_consent=abc2&ip=&pt=gross&rp=4&stid=&bWlkPTEmcmN1cj1VU0Q"
+        "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=1&gdpr_consent=abc2&ip=&pt=gross&rp=4&stid=&bWlkPTEmcmN1cj1VU0Q"
       },
       "mockResponse": {
         "status": 204
diff --git a/adapters/decenterads/decenterads_test.go b/adapters/decenterads/decenterads_test.go
new file mode 100644
index 00000000..ca86e891
--- /dev/null
+++ b/adapters/decenterads/decenterads_test.go
@@ -0,0 +1,18 @@
+package decenterads
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderDecenterAds, config.Adapter{
+		Endpoint: "http://example.com/?c=o&m=ortb"})
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+	adapterstest.RunJSONBidderTest(t, "decenteradstest", bidder)
+}
diff --git a/adapters/decenterads/decenteradstest/exemplary/simple-banner.json b/adapters/decenterads/decenteradstest/exemplary/simple-banner.json
new file mode 100644
index 00000000..fd2c09d0
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/exemplary/simple-banner.json
@@ -0,0 +1,128 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+        {
+            "id": "test-imp-id",
+            "banner": {
+                "format": [
+                    {
+                      "w": 300,
+                      "h": 250
+                    },
+                    {
+                      "w": 300,
+                      "h": 600
+                    }
+                ]
+            },
+            "ext": {
+                "bidder": {
+                  "placementId": "3"
+                }
+            }
+        }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    }
+},
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+              {
+                "id": "test-imp-id",
+                "banner": {
+                  "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+                },
+              "ext": {
+                "placementId": "3"
+              }
+            }
+          ],
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "device": {
+            "ip": "123.123.123.123",
+            "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "test_bid_id",
+                    "impid": "test-imp-id",
+                    "price": 0.27543,
+                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://supply.decenterads.com/?c=o&m=adm&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                    "cid": "test_cid",
+                    "crid": "test_crid",
+                    "dealid": "test_dealid",
+                    "w": 300,
+                    "h": 250,
+                    "ext": {
+                      "prebid": {
+                        "type": "banner"
+                      }
+                    }
+                  }
+                ]
+              }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "bids":[
+        {
+          "bid":  {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://supply.decenterads.com/?c=o&m=adm&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/exemplary/simple-video.json b/adapters/decenterads/decenteradstest/exemplary/simple-video.json
new file mode 100644
index 00000000..b85d302b
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/exemplary/simple-video.json
@@ -0,0 +1,116 @@
+{
+  "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",
+        "video": {
+          "mimes": ["video/mp4"],
+          "protocols": [2, 5],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "3"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "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",
+              "video": {
+                "mimes": ["video/mp4"],
+                "protocols": [2, 5],
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "placementId": "3"
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+          {
+            "bid": [
+              {
+                "id": "test_bid_id",
+                "impid": "test-imp-id",
+                "price": 0.27543,
+                "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                "cid": "test_cid",
+                "crid": "test_crid",
+                "dealid": "test_dealid",
+                "ext": {
+                  "prebid": {
+                    "type": "video"
+                  }
+                }
+              }
+            ],
+            "seat": "decenterads"
+          }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "ext": {
+              "prebid": {
+                "type": "video"
+              }
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json b/adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000..8f8aa0e9
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/exemplary/simple-web-banner.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "3"
+          }
+        }
+      }
+    ],
+    "site": {
+      "id": "1",
+      "domain": "test.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "ext": {
+                "placementId": "3"
+              }
+            }
+          ],
+          "site": {
+            "id": "1",
+            "domain": "test.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test_bid_id",
+                  "impid": "test-imp-id",
+                  "price": 0.27543,
+                  "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://supply.decenterads.com/?c=o&m=adm&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                  "cid": "test_cid",
+                  "crid": "test_crid",
+                  "dealid": "test_dealid",
+                  "w": 468,
+                  "h": 60,
+                  "ext": {
+                    "prebid": {
+                      "type": "banner"
+                    }
+                  }
+                }
+              ],
+              "seat": "decenterads"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"http://supply.decenterads.com/?c=o&m=adm&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "w": 468,
+            "h": 60,
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/params/race/banner.json b/adapters/decenterads/decenteradstest/params/race/banner.json
new file mode 100644
index 00000000..dbdac1ad
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/params/race/banner.json
@@ -0,0 +1,3 @@
+{
+  "TagID": "6"
+}
\ No newline at end of file
diff --git a/adapters/decenterads/decenteradstest/params/race/video.json b/adapters/decenterads/decenteradstest/params/race/video.json
new file mode 100644
index 00000000..6e2e0b38
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/params/race/video.json
@@ -0,0 +1,3 @@
+{
+  "TagID": "7"
+}
\ No newline at end of file
diff --git a/adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json b/adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json
new file mode 100644
index 00000000..83455ca7
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/bad-imp-ext.json
@@ -0,0 +1,41 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "decenterads": {
+            "placementId": "1"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "bidder parameters required",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/supplemental/bad_response.json b/adapters/decenterads/decenteradstest/supplemental/bad_response.json
new file mode 100644
index 00000000..9d9d977b
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/bad_response.json
@@ -0,0 +1,83 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "1"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "ext": {
+                "placementId": "1"
+              }
+            }
+          ],
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": ""
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/supplemental/bad_status_code.json b/adapters/decenterads/decenteradstest/supplemental/bad_status_code.json
new file mode 100644
index 00000000..4a81da36
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/bad_status_code.json
@@ -0,0 +1,77 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "1"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {}
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "ext": {
+                "placementId": "1"
+              }
+            }
+          ],
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "device": {}
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unexpected status code: 400",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json b/adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json
new file mode 100644
index 00000000..db4053f2
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/imp_ext_empty_object.json
@@ -0,0 +1,37 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {}
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "bidder parameters required",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json b/adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json
new file mode 100644
index 00000000..db938d6a
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/imp_ext_string.json
@@ -0,0 +1,37 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": ""
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "unable to parse bidder parameers: json: cannot unmarshal string into Go value of type map[string]json.RawMessage",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/supplemental/status-204.json b/adapters/decenterads/decenteradstest/supplemental/status-204.json
new file mode 100644
index 00000000..3eff0fdc
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/status-204.json
@@ -0,0 +1,78 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "1"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "ext": {
+                "placementId": "1"
+              }
+            }
+          ],
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+          }
+        }
+      },
+      "expectedBidResponses": [],
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ]
+}
diff --git a/adapters/decenterads/decenteradstest/supplemental/status-404.json b/adapters/decenterads/decenteradstest/supplemental/status-404.json
new file mode 100644
index 00000000..a162dd89
--- /dev/null
+++ b/adapters/decenterads/decenteradstest/supplemental/status-404.json
@@ -0,0 +1,83 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "1"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://example.com/?c=o&m=ortb",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "ext": {
+                "placementId": "1"
+              }
+            }
+          ],
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 404,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unexpected status code: 404",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/decenterads/params_test.go b/adapters/decenterads/params_test.go
new file mode 100644
index 00000000..3d3708be
--- /dev/null
+++ b/adapters/decenterads/params_test.go
@@ -0,0 +1,48 @@
+package decenterads
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// TestValidParams makes sure that the decenterads schema accepts all imp.ext fields which we intend to support.
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderDecenterAds, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected decenterads params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the decenterads schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderDecenterAds, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placementId": "11"}`,
+}
+
+var invalidParams = []string{
+	`{"id": "456"}`,
+	`{"placementid": "3456"}`,
+	`{"placement_id": 346}`,
+	`{"placementID": ""}`,
+	`{"placementId": 234}`,
+}
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/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index bca8623d..818303b8 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -78,6 +78,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,
@@ -101,6 +102,7 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderAdot:         true,
 		openrtb_ext.BidderAdprime:      true,
 		openrtb_ext.BidderApplogy:      true,
+		openrtb_ext.BidderDecenterAds:  true,
 		openrtb_ext.BidderInMobi:       true,
 		openrtb_ext.BidderKidoz:        true,
 		openrtb_ext.BidderKubient:      true,
@@ -111,7 +113,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,
 	}