Repository: prebid/prebid-server
Pull request #1669 (2021-01-21 13:09:03)

New Adapter: DecenterAds


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/amx/amx.go b/adapters/amx/amx.go
index 6ce1574a..a9dfd06f 100644
--- a/adapters/amx/amx.go
+++ b/adapters/amx/amx.go
@@ -17,7 +17,7 @@ import (
 const vastImpressionFormat = "<Impression><![CDATA[%s]]></Impression>"
 const vastSearchPoint = "</Impression>"
 const nbrHeaderName = "x-nbr"
-const adapterVersion = "pbs1.0"
+const adapterVersion = "pbs1.1"
 
 // AMXAdapter is the AMX bid adapter
 type AMXAdapter struct {
@@ -149,6 +149,7 @@ func (adapter *AMXAdapter) MakeBids(request *openrtb.BidRequest, externalRequest
 
 	for _, sb := range bidResp.SeatBid {
 		for _, bid := range sb.Bid {
+			bid := bid
 			bidExt, bidExtErr := getBidExt(bid.Ext)
 			if bidExtErr != nil {
 				errs = append(errs, bidExtErr)
@@ -165,11 +166,12 @@ func (adapter *AMXAdapter) MakeBids(request *openrtb.BidRequest, externalRequest
 				// remove the NURL so a client/player doesn't fire it twice
 				b.Bid.NURL = ""
 			}
+
 			bidResponse.Bids = append(bidResponse.Bids, b)
 		}
 	}
-	return bidResponse, errs
 
+	return bidResponse, errs
 }
 
 func getBidExt(ext json.RawMessage) (amxBidExt, error) {
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/eplanning/eplanning.go b/adapters/eplanning/eplanning.go
index 21c61389..8d0f7e8b 100644
--- a/adapters/eplanning/eplanning.go
+++ b/adapters/eplanning/eplanning.go
@@ -26,6 +26,9 @@ const sec = "ROS"
 const dfpClientID = "1"
 const requestTargetInventory = "1"
 
+var priorityOrderForMobileSizesAsc = []string{"1x1", "300x50", "320x50", "300x250"}
+var priorityOrderForDesktopSizesAsc = []string{"1x1", "970x90", "970x250", "160x600", "300x600", "728x90", "300x250"}
+
 var cleanNameSteps = []cleanNameStep{
 	{regexp.MustCompile(`_|\.|-|\/`), ""},
 	{regexp.MustCompile(`\)\(|\(|\)|:`), "_"},
@@ -67,10 +70,11 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqIn
 	spacesStrings := make([]string, 0, totalImps)
 	totalRequests := 0
 	clientID := ""
+	isMobile := isMobileDevice(request)
 
 	for i := 0; i < totalImps; i++ {
 		imp := request.Imp[i]
-		extImp, err := verifyImp(&imp)
+		extImp, err := verifyImp(&imp, isMobile)
 		if err != nil {
 			errors = append(errors, err)
 			continue
@@ -187,6 +191,10 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb.BidRequest, reqIn
 	return requests, errors
 }
 
+func isMobileDevice(request *openrtb.BidRequest) bool {
+	return request.Device != nil && (request.Device.DeviceType == openrtb.DeviceTypeMobileTablet || request.Device.DeviceType == openrtb.DeviceTypePhone || request.Device.DeviceType == openrtb.DeviceTypeTablet)
+}
+
 func cleanName(name string) string {
 	for _, step := range cleanNameSteps {
 		name = step.expression.ReplaceAllString(name, step.replacementString)
@@ -194,7 +202,7 @@ func cleanName(name string) string {
 	return name
 }
 
-func verifyImp(imp *openrtb.Imp) (*openrtb_ext.ExtImpEPlanning, error) {
+func verifyImp(imp *openrtb.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning, error) {
 	var bidderExt adapters.ExtImpBidder
 
 	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
@@ -217,7 +225,7 @@ func verifyImp(imp *openrtb.Imp) (*openrtb_ext.ExtImpEPlanning, error) {
 		}
 	}
 
-	width, height := getSizeFromImp(imp)
+	width, height := getSizeFromImp(imp, isMobile)
 
 	if width == 0 && height == 0 {
 		impExt.SizeString = nullSize
@@ -232,17 +240,34 @@ func verifyImp(imp *openrtb.Imp) (*openrtb_ext.ExtImpEPlanning, error) {
 	return &impExt, nil
 }
 
-func getSizeFromImp(imp *openrtb.Imp) (uint64, uint64) {
+func searchSizePriority(hashedFormats map[string]int, format []openrtb.Format, priorityOrderForSizesAsc []string) (uint64, uint64) {
+	for i := len(priorityOrderForSizesAsc) - 1; i >= 0; i-- {
+		if formatIndex, wasFound := hashedFormats[priorityOrderForSizesAsc[i]]; wasFound {
+			return format[formatIndex].W, format[formatIndex].H
+		}
+	}
+	return format[0].W, format[0].H
+}
+
+func getSizeFromImp(imp *openrtb.Imp, isMobile bool) (uint64, uint64) {
 	if imp.Banner.W != nil && imp.Banner.H != nil {
 		return *imp.Banner.W, *imp.Banner.H
 	}
 
 	if imp.Banner.Format != nil {
-		for _, format := range imp.Banner.Format {
+		hashedFormats := make(map[string]int, len(imp.Banner.Format))
+
+		for i, format := range imp.Banner.Format {
 			if format.W != 0 && format.H != 0 {
-				return format.W, format.H
+				hashedFormats[fmt.Sprintf("%dx%d", format.W, format.H)] = i
 			}
 		}
+
+		if isMobile {
+			return searchSizePriority(hashedFormats, imp.Banner.Format, priorityOrderForMobileSizesAsc)
+		} else {
+			return searchSizePriority(hashedFormats, imp.Banner.Format, priorityOrderForDesktopSizesAsc)
+		}
 	}
 
 	return 0, 0
@@ -278,12 +303,14 @@ func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb.BidRequest, e
 		}}
 	}
 
+	isMobile := isMobileDevice(internalRequest)
+
 	bidResponse := adapters.NewBidderResponse()
 
 	spaceNameToImpID := make(map[string]string)
 
 	for _, imp := range internalRequest.Imp {
-		extImp, err := verifyImp(&imp)
+		extImp, err := verifyImp(&imp, isMobile)
 		if err != nil {
 			continue
 		}
diff --git a/adapters/info.go b/adapters/info.go
index 7f3ad9c3..19897ac7 100644
--- a/adapters/info.go
+++ b/adapters/info.go
@@ -206,6 +206,11 @@ type BidderInfo struct {
 	Capabilities            *CapabilitiesInfo `yaml:"capabilities" json:"capabilities"`
 	AliasOf                 string            `json:"aliasOf,omitempty"`
 	ModifyingVastXmlAllowed bool              `yaml:"modifyingVastXmlAllowed" json:"-" xml:"-"`
+	Debug                   *DebugInfo        `yaml:"debug,omitempty" json:"-" xml:"-"`
+}
+
+type DebugInfo struct {
+	Allow bool `yaml:"allow" json:"allow"`
 }
 
 type MaintainerInfo struct {
diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go
index 23f11f9a..b07a2cba 100644
--- a/adapters/pulsepoint/pulsepoint.go
+++ b/adapters/pulsepoint/pulsepoint.go
@@ -1,17 +1,19 @@
 package pulsepoint
 
 import (
-	"bytes"
-	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"strconv"
+
+	"bytes"
+	"context"
+	"io/ioutil"
 	"strings"
 
 	"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"
 	"github.com/prebid/prebid-server/pbs"
@@ -23,15 +25,169 @@ type PulsePointAdapter struct {
 	URI  string
 }
 
+// Builds an instance of PulsePointAdapter
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &PulsePointAdapter{
+		URI: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	errs := make([]error, 0, len(request.Imp))
+
+	var err error
+	pubID := ""
+	imps := make([]openrtb.Imp, 0, len(request.Imp))
+	for i := 0; i < len(request.Imp); i++ {
+		imp := request.Imp[i]
+		var bidderExt adapters.ExtImpBidder
+		if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			errs = append(errs, &errortypes.BadInput{
+				Message: err.Error(),
+			})
+			continue
+		}
+		var pulsepointExt openrtb_ext.ExtImpPulsePoint
+		if err = json.Unmarshal(bidderExt.Bidder, &pulsepointExt); err != nil {
+			errs = append(errs, &errortypes.BadInput{
+				Message: err.Error(),
+			})
+			continue
+		}
+		// parse pubid and keep it for reference
+		if pubID == "" && pulsepointExt.PubID > 0 {
+			pubID = strconv.Itoa(pulsepointExt.PubID)
+		}
+		// tag id to be sent
+		imp.TagID = strconv.Itoa(pulsepointExt.TagID)
+		imps = append(imps, imp)
+	}
+
+	// verify there are valid impressions
+	if len(imps) == 0 {
+		return nil, errs
+	}
+
+	// add the publisher id from ext to the site.pub.id or app.pub.id
+	if request.Site != nil {
+		site := *request.Site
+		if site.Publisher != nil {
+			publisher := *site.Publisher
+			publisher.ID = pubID
+			site.Publisher = &publisher
+		} else {
+			site.Publisher = &openrtb.Publisher{ID: pubID}
+		}
+		request.Site = &site
+	} else if request.App != nil {
+		app := *request.App
+		if app.Publisher != nil {
+			publisher := *app.Publisher
+			publisher.ID = pubID
+			app.Publisher = &publisher
+		} else {
+			app.Publisher = &openrtb.Publisher{ID: pubID}
+		}
+		request.App = &app
+	}
+
+	request.Imp = imps
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	return []*adapters.RequestData{{
+		Method:  "POST",
+		Uri:     a.URI,
+		Body:    reqJSON,
+		Headers: headers,
+	}}, errs
+}
+
+func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	// passback
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+	// bad requests
+	if response.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode),
+		}}
+	}
+	// error
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode),
+		}}
+	}
+	// parse response
+	var bidResp openrtb.BidResponse
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+	// map imps by id
+	impsByID := make(map[string]openrtb.Imp)
+	for i := 0; i < len(internalRequest.Imp); i++ {
+		impsByID[internalRequest.Imp[i].ID] = internalRequest.Imp[i]
+	}
+
+	var errs []error
+	for _, sb := range bidResp.SeatBid {
+		for i := 0; i < len(sb.Bid); i++ {
+			bid := sb.Bid[i]
+			imp := impsByID[bid.ImpID]
+			bidType := getBidType(imp)
+			if &imp != nil && bidType != "" {
+				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+					Bid:     &bid,
+					BidType: bidType,
+				})
+			}
+		}
+	}
+	return bidResponse, errs
+}
+
+func getBidType(imp openrtb.Imp) openrtb_ext.BidType {
+	// derive the bidtype purely from the impression itself
+	if imp.Banner != nil {
+		return openrtb_ext.BidTypeBanner
+	} else if imp.Video != nil {
+		return openrtb_ext.BidTypeVideo
+	} else if imp.Audio != nil {
+		return openrtb_ext.BidTypeAudio
+	} else if imp.Native != nil {
+		return openrtb_ext.BidTypeNative
+	}
+	return ""
+}
+
+/////////////////////////////////
+// Legacy implementation: Start
+/////////////////////////////////
+
+func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter {
+	a := adapters.NewHTTPAdapter(config)
+
+	return &PulsePointAdapter{
+		http: a,
+		URI:  uri,
+	}
+}
+
 // used for cookies and such
 func (a *PulsePointAdapter) Name() string {
 	return "pulsepoint"
 }
 
-func (a *PulsePointAdapter) SkipNoCookies() bool {
-	return false
-}
-
 // parameters for pulsepoint adapter.
 type PulsepointParams struct {
 	PublisherId int    `json:"cp"`
@@ -39,6 +195,10 @@ type PulsepointParams struct {
 	AdSize      string `json:"cf"`
 }
 
+func (a *PulsePointAdapter) SkipNoCookies() bool {
+	return false
+}
+
 func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) {
 	mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}
 	ppReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes)
@@ -195,11 +355,6 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde
 	return bids, nil
 }
 
-func NewPulsePointLegacyAdapter(config *adapters.HTTPAdapterConfig, uri string) *PulsePointAdapter {
-	a := adapters.NewHTTPAdapter(config)
-
-	return &PulsePointAdapter{
-		http: a,
-		URI:  uri,
-	}
-}
+/////////////////////////////////
+// Legacy implementation: End
+/////////////////////////////////
diff --git a/config/accounts.go b/config/accounts.go
index 0faf5f5f..54809245 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -19,6 +19,7 @@ type Account struct {
 	EventsEnabled bool        `mapstructure:"events_enabled" json:"events_enabled"`
 	CCPA          AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
 	GDPR          AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
+	DebugAllow    bool        `mapstructure:"debug_allow" json:"debug_allow"`
 }
 
 // AccountCCPA represents account-specific CCPA configuration
diff --git a/config/config.go b/config/config.go
index 6577c1fb..ae1f62e9 100755
--- a/config/config.go
+++ b/config/config.go
@@ -217,7 +217,7 @@ func (cfg *GDPR) validate(errs []error) []error {
 		errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)"))
 	}
 	if cfg.TCF1.FetchGVL == true {
-		glog.Warning("gdpr.tcf1.fetch_gvl is deprecated and will be removed in a future version, at which point TCF1 will always use the fallback GVL")
+		errs = append(errs, fmt.Errorf("gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward"))
 	}
 	return errs
 }
@@ -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")
@@ -601,7 +602,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGrid, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGumGum, "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D")
-	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=184932&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
+	// openrtb_ext.BidderIx doesn't have a good default.
 	// openrtb_ext.BidderInvibes doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderKrushmedia, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24")
@@ -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")
@@ -910,7 +912,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0)
 	v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0)
 	v.SetDefault("gdpr.non_standard_publishers", []string{""})
-	v.SetDefault("gdpr.tcf1.fetch_gvl", true)
+	v.SetDefault("gdpr.tcf1.fetch_gvl", false)
 	v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./static/tcf1/fallback_gvl.json")
 	v.SetDefault("gdpr.tcf2.enabled", true)
 	v.SetDefault("gdpr.tcf2.purpose1.enabled", true)
@@ -937,6 +939,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("blacklisted_accts", []string{""})
 	v.SetDefault("account_required", false)
 	v.SetDefault("account_defaults.disabled", false)
+	v.SetDefault("account_defaults.debug_allow", true)
 	v.SetDefault("certificates_file", "")
 	v.SetDefault("auto_gen_source_tid", true)
 
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 31342a4c..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"
@@ -65,6 +66,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/orbidder"
 	"github.com/prebid/prebid-server/adapters/pubmatic"
 	"github.com/prebid/prebid-server/adapters/pubnative"
+	"github.com/prebid/prebid-server/adapters/pulsepoint"
 	"github.com/prebid/prebid-server/adapters/revcontent"
 	"github.com/prebid/prebid-server/adapters/rhythmone"
 	"github.com/prebid/prebid-server/adapters/rtbhouse"
@@ -135,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,
@@ -166,6 +169,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderOrbidder:         orbidder.Builder,
 		openrtb_ext.BidderPubmatic:         pubmatic.Builder,
 		openrtb_ext.BidderPubnative:        pubnative.Builder,
+		openrtb_ext.BidderPulsepoint:       pulsepoint.Builder,
 		openrtb_ext.BidderRevcontent:       revcontent.Builder,
 		openrtb_ext.BidderRhythmone:        rhythmone.Builder,
 		openrtb_ext.BidderRTBHouse:         rtbhouse.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index 6e12ed00..7e271376 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adapters/lifestreet"
-	"github.com/prebid/prebid-server/adapters/pulsepoint"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -39,7 +38,12 @@ func buildExchangeBidders(cfg *config.Configuration, infos adapters.BidderInfos,
 
 	exchangeBidders := make(map[openrtb_ext.BidderName]adaptedBidder, len(bidders))
 	for bidderName, bidder := range bidders {
-		exchangeBidders[bidderName] = adaptBidder(bidder, client, cfg, me, bidderName)
+		info, infoFound := infos[string(bidderName)]
+		if !infoFound {
+			errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder))
+			continue
+		}
+		exchangeBidders[bidderName] = adaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
 	}
 
 	return exchangeBidders, nil
@@ -58,7 +62,7 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos adapters.Bidder
 		}
 
 		// Ignore Legacy Bidders
-		if bidderName == openrtb_ext.BidderLifestreet || bidderName == openrtb_ext.BidderPulsepoint {
+		if bidderName == openrtb_ext.BidderLifestreet {
 			continue
 		}
 
@@ -99,12 +103,6 @@ func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos a
 		bidders[openrtb_ext.BidderLifestreet] = adaptLegacyAdapter(adapter)
 	}
 
-	// Pulsepoint
-	if infos[string(openrtb_ext.BidderPulsepoint)].Status == adapters.StatusActive {
-		adapter := pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, adapterConfig[string(openrtb_ext.BidderPulsepoint)].Endpoint)
-		bidders[openrtb_ext.BidderPulsepoint] = adaptLegacyAdapter(adapter)
-	}
-
 	return bidders
 }
 
diff --git a/exchange/bidder.go b/exchange/bidder.go
index e4867b0e..5b5852bd 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -49,7 +49,7 @@ type adaptedBidder interface {
 	//
 	// Any errors will be user-facing in the API.
 	// Error messages should help publishers understand what might account for "bad" bids.
-	requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error)
+	requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error)
 }
 
 // pbsOrtbBid is a Bid returned by an adaptedBidder.
@@ -93,7 +93,7 @@ type pbsOrtbSeatBid struct {
 //
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
-func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName) adaptedBidder {
+func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *adapters.DebugInfo) adaptedBidder {
 	return &bidderAdapter{
 		Bidder:     bidder,
 		BidderName: name,
@@ -102,10 +102,18 @@ func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Config
 		config: bidderAdapterConfig{
 			Debug:              cfg.Debug,
 			DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics,
+			DebugInfo:          adapters.DebugInfo{Allow: parseDebugInfo(debugInfo)},
 		},
 	}
 }
 
+func parseDebugInfo(info *adapters.DebugInfo) bool {
+	if info == nil {
+		return true
+	}
+	return info.Allow
+}
+
 type bidderAdapter struct {
 	Bidder     adapters.Bidder
 	BidderName openrtb_ext.BidderName
@@ -117,9 +125,10 @@ type bidderAdapter struct {
 type bidderAdapterConfig struct {
 	Debug              config.Debug
 	DisableConnMetrics bool
+	DebugInfo          adapters.DebugInfo
 }
 
-func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
+func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
 	reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo)
 
 	if len(reqData) == 0 {
@@ -155,8 +164,14 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi
 	for i := 0; i < len(reqData); i++ {
 		httpInfo := <-responseChannel
 		// If this is a test bid, capture debugging info from the requests.
-		if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) {
-			seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+		// Write debug data to ext in case if:
+		// - debugContextKey (url param) in true
+		// - account debug is allowed
+		// - bidder debug is allowed
+		if accountDebugAllowed && bidder.config.DebugInfo.Allow {
+			if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) {
+				seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+			}
 		}
 
 		if httpInfo.err == nil {
@@ -310,17 +325,19 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset
 func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
 	if httpInfo.err == nil {
 		return &openrtb_ext.ExtHttpCall{
-			Uri:          httpInfo.request.Uri,
-			RequestBody:  string(httpInfo.request.Body),
-			ResponseBody: string(httpInfo.response.Body),
-			Status:       httpInfo.response.StatusCode,
+			Uri:            httpInfo.request.Uri,
+			RequestBody:    string(httpInfo.request.Body),
+			ResponseBody:   string(httpInfo.response.Body),
+			Status:         httpInfo.response.StatusCode,
+			RequestHeaders: httpInfo.request.Headers,
 		}
 	} else if httpInfo.request == nil {
 		return &openrtb_ext.ExtHttpCall{}
 	} else {
 		return &openrtb_ext.ExtHttpCall{
-			Uri:         httpInfo.request.Uri,
-			RequestBody: string(httpInfo.request.Body),
+			Uri:            httpInfo.request.Uri,
+			RequestBody:    string(httpInfo.request.Body),
+			RequestHeaders: httpInfo.request.Headers,
 		}
 	}
 }
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index 3700bac4..cf74dfb1 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -28,8 +28,8 @@ type validatedBidder struct {
 	bidder adaptedBidder
 }
 
-func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
-	seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo)
+func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
+	seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed)
 	if validationErrors := removeInvalidBids(request, seatBid); len(validationErrors) > 0 {
 		errs = append(errs, validationErrors...)
 	}
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 99ac8a88..cfb9c6d3 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -14,7 +14,6 @@ import (
 	"strings"
 	"time"
 
-	uuid "github.com/gofrs/uuid"
 	"github.com/prebid/prebid-server/stored_requests"
 
 	"github.com/golang/glog"
@@ -135,7 +134,15 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		_, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
 	}
 
+	if debugLog == nil {
+		debugLog = &DebugLog{Enabled: false}
+	}
+
 	debugInfo := getDebugInfo(r.BidRequest, requestExt)
+
+	debugInfo = debugInfo && r.Account.DebugAllow
+	debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow
+
 	if debugInfo {
 		ctx = e.makeDebugContext(ctx, debugInfo)
 	}
@@ -163,10 +170,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	// Get currency rates conversions for the auction
 	conversions := e.currencyConverter.Rates()
 
-	adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions)
+	adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow)
 
 	var auc *auction
 	var cacheErrs []error
+	var bidResponseExt *openrtb_ext.ExtBidResponse
 	if anyBidsReturned {
 
 		var bidCategory map[string]string
@@ -194,34 +202,35 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 				dealErrs := applyDealSupport(r.BidRequest, auc, bidCategory)
 				errs = append(errs, dealErrs...)
 			}
-			cacheErrs := auc.doCache(ctx, e.cache, targData, evTracking, r.BidRequest, 60, &r.Account.CacheTTL, bidCategory, debugLog)
+
+			bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
+			if debugLog.Enabled {
+				if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
+					debugLog.Data.Response = string(bidRespExtBytes)
+				} else {
+					debugLog.Data.Response = "Unable to marshal response ext for debugging"
+					errs = append(errs, err)
+				}
+			}
+
+			cacheErrs = auc.doCache(ctx, e.cache, targData, evTracking, r.BidRequest, 60, &r.Account.CacheTTL, bidCategory, debugLog)
 			if len(cacheErrs) > 0 {
 				errs = append(errs, cacheErrs...)
 			}
+
 			targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory)
 
 		}
-	}
+		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
+	} else {
+		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
 
-	bidResponseExt := e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs)
+		if debugLog.Enabled {
 
-	// Ensure caching errors are added in case auc.doCache was called and errors were returned
-	if len(cacheErrs) > 0 {
-		bidderCacheErrs := errsToBidderErrors(cacheErrs)
-		bidResponseExt.Errors[openrtb_ext.PrebidExtKey] = append(bidResponseExt.Errors[openrtb_ext.PrebidExtKey], bidderCacheErrs...)
-	}
-
-	if debugLog != nil && debugLog.Enabled {
-		if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
-			debugLog.Data.Response = string(bidRespExtBytes)
-		} else {
-			debugLog.Data.Response = "Unable to marshal response ext for debugging"
-			errs = append(errs, err)
-		}
-		if !anyBidsReturned {
-			if rawUUID, err := uuid.NewV4(); err == nil {
-				debugLog.CacheKey = rawUUID.String()
+			if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
+				debugLog.Data.Response = string(bidRespExtBytes)
 			} else {
+				debugLog.Data.Response = "Unable to marshal response ext for debugging"
 				errs = append(errs, err)
 			}
 		}
@@ -342,7 +351,8 @@ func (e *exchange) getAllBids(
 	ctx context.Context,
 	bidderRequests []BidderRequest,
 	bidAdjustments map[string]float64,
-	conversions currency.Conversions) (
+	conversions currency.Conversions,
+	accountDebugAllowed bool) (
 	map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra, bool) {
 	// Set up pointers to the bid results
@@ -373,7 +383,7 @@ func (e *exchange) getAllBids(
 			}
 			var reqInfo adapters.ExtraRequestInfo
 			reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
-			bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo)
+			bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed)
 
 			// Add in time reporting
 			elapsed := time.Since(start)
@@ -758,7 +768,7 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb
 
 	for bidderName, responseExtra := range adapterExtra {
 
-		if debugInfo {
+		if debugInfo && len(responseExtra.HttpCalls) > 0 {
 			bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls
 		}
 		// Only make an entry for bidder errors if the bidder reported any.
diff --git a/exchange/legacy.go b/exchange/legacy.go
index dc81e7a1..b4845b76 100644
--- a/exchange/legacy.go
+++ b/exchange/legacy.go
@@ -33,7 +33,7 @@ type adaptedAdapter struct {
 //
 // This is not ideal. OpenRTB provides a superset of the legacy data structures.
 // For requests which use those features, the best we can do is respond with "no bid".
-func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
+func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
 	legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name)
 	if legacyRequest == nil || legacyBidder == nil {
 		return nil, errs
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index eea7fa5a..bfc238e1 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -34,18 +34,25 @@ const (
 
 // NewPermissions gets an instance of the Permissions for use elsewhere in the project.
 func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ext.BidderName]uint16, client *http.Client) Permissions {
-	// If the host doesn't buy into the IAB GDPR consent framework, then save some cycles and let all syncs happen.
-	if cfg.HostVendorID == 0 {
-		return AlwaysAllow{}
+	if !cfg.Enabled {
+		return &AlwaysAllow{}
 	}
 
-	return &permissionsImpl{
+	permissionsImpl := &permissionsImpl{
 		cfg:       cfg,
 		vendorIDs: vendorIDs,
 		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
 			tcf1SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf1SpecVersion),
 			tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf2SpecVersion)},
 	}
+
+	if cfg.HostVendorID == 0 {
+		return &AllowHostCookies{
+			permissionsImpl: permissionsImpl,
+		}
+	}
+
+	return permissionsImpl
 }
 
 // An ErrorMalformedConsent will be returned by the Permissions interface if
diff --git a/gdpr/impl.go b/gdpr/impl.go
index e1822ece..c7998251 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -223,6 +223,16 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons
 	return
 }
 
+// AllowHostCookies represents a GDPR permissions policy with host cookie syncing always allowed
+type AllowHostCookies struct {
+	*permissionsImpl
+}
+
+// HostCookiesAllowed always returns true
+func (p *AllowHostCookies) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
+	return true, nil
+}
+
 // Exporting to allow for easy test setups
 type AlwaysAllow struct{}
 
diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go
index de6da7b4..95a17109 100644
--- a/gdpr/vendorlist-fetching.go
+++ b/gdpr/vendorlist-fetching.go
@@ -28,22 +28,21 @@ type saveVendors func(uint16, api.VendorList)
 
 func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, tcfSpecVersion uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
 	var fallback api.VendorList
-	if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) > 0 {
-		fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath)
-	}
 
-	// If we are not going to try fetching the GVL dynamically, we have a simple fetcher.
-	if !cfg.TCF1.FetchGVL && tcfSpecVersion == tcf1SpecVersion {
-		if fallback != nil {
-			return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
-				return fallback, nil
-			}
-		}
+	if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) == 0 {
 		return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
 			return nil, makeVendorListNotFoundError(vendorListVersion)
 		}
 	}
 
+	if tcfSpecVersion == tcf1SpecVersion {
+		fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath)
+
+		return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) {
+			return fallback, nil
+		}
+	}
+
 	cacheSave, cacheLoad := newVendorListCache(fallback)
 
 	preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
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/openrtb_ext/imp_pulsepoint.go b/openrtb_ext/imp_pulsepoint.go
new file mode 100644
index 00000000..c168c80f
--- /dev/null
+++ b/openrtb_ext/imp_pulsepoint.go
@@ -0,0 +1,9 @@
+package openrtb_ext
+
+// ExtImpPulsePoint defines the json spec for bidrequest.imp[i].ext.pulsepoint
+// PubId/TagId are mandatory params
+
+type ExtImpPulsePoint struct {
+	PubID int `json:"cp"`
+	TagID int `json:"ct"`
+}
diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go
index 589dad40..02370d19 100644
--- a/openrtb_ext/response.go
+++ b/openrtb_ext/response.go
@@ -55,10 +55,11 @@ type ExtBidderError struct {
 
 // ExtHttpCall defines the contract for a bidresponse.ext.debug.httpcalls.{bidder}[i]
 type ExtHttpCall struct {
-	Uri          string `json:"uri"`
-	RequestBody  string `json:"requestbody"`
-	ResponseBody string `json:"responsebody"`
-	Status       int    `json:"status"`
+	Uri            string              `json:"uri"`
+	RequestBody    string              `json:"requestbody"`
+	RequestHeaders map[string][]string `json:"requestheaders"`
+	ResponseBody   string              `json:"responsebody"`
+	Status         int                 `json:"status"`
 }
 
 // CookieStatus describes the allowed values for bidresponse.ext.usersync.{bidder}.status
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/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml
index 716e4530..056a0bf3 100644
--- a/static/bidder-info/pulsepoint.yaml
+++ b/static/bidder-info/pulsepoint.yaml
@@ -4,6 +4,12 @@ capabilities:
   app:
     mediaTypes:
       - banner
+      - video
+      - audio
+      - native
   site:
     mediaTypes:
       - banner
+      - video
+      - audio
+      - native
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/static/bidder-params/pulsepoint.json b/static/bidder-params/pulsepoint.json
index c4704c3b..673fd2ef 100644
--- a/static/bidder-params/pulsepoint.json
+++ b/static/bidder-params/pulsepoint.json
@@ -11,12 +11,7 @@
     "ct": {
       "type": "integer",
       "description": "An ID which identifies the ad slot being sold"
-    },
-    "cf": {
-      "type": "string",
-      "pattern": "^[0-9]+[xX][0-9]+$",
-      "description": "The size of the ad slot being sold. This should be a string like 300X250"
     }
   },
-  "required": ["cp", "ct", "cf"]
+  "required": ["cp", "ct"]
 }


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/admixer/usersync_test.go b/adapters/admixer/usersync_test.go
index d31f7b10..79f023a2 100644
--- a/adapters/admixer/usersync_test.go
+++ b/adapters/admixer/usersync_test.go
@@ -11,7 +11,7 @@ import (
 )
 
 func TestAdmixerSyncer(t *testing.T) {
-	syncURL := "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl=localhost%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24"
+	syncURL := "http://anyHost/anyPath"
 	syncURLTemplate := template.Must(
 		template.New("sync-template").Parse(syncURL),
 	)
@@ -28,7 +28,7 @@ func TestAdmixerSyncer(t *testing.T) {
 	})
 
 	assert.NoError(t, err)
-	assert.Equal(t, "https://inv-nets.admixer.net/adxcm.aspx?gdpr=A&gdpr_consent=B&us_privacy=C&redir=1&rurl=localhost%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%24visitor_cookie%24%24", syncInfo.URL)
+	assert.Equal(t, "http://anyHost/anyPath", syncInfo.URL)
 	assert.Equal(t, "redirect", syncInfo.Type)
 	assert.EqualValues(t, 511, syncer.GDPRVendorID())
 	assert.Equal(t, false, syncInfo.SupportCORS)
diff --git a/adapters/amx/amxtest/exemplary/app-simple.json b/adapters/amx/amxtest/exemplary/app-simple.json
index b2f53849..c208bff9 100644
--- a/adapters/amx/amxtest/exemplary/app-simple.json
+++ b/adapters/amx/amxtest/exemplary/app-simple.json
@@ -64,7 +64,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0",
+        "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
         "body": {
           "app": {
             "bundle": "639881495",
diff --git a/adapters/amx/amxtest/exemplary/display-multiple.json b/adapters/amx/amxtest/exemplary/display-multiple.json
new file mode 100644
index 00000000..f885bdb2
--- /dev/null
+++ b/adapters/amx/amxtest/exemplary/display-multiple.json
@@ -0,0 +1,292 @@
+{
+  "mockBidRequest": {
+    "device": {
+      "dnt": 0,
+      "h": 1120,
+      "ip": "98.249.0.0",
+      "language": "en",
+      "os": "macos",
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
+      "w": 1792
+    },
+    "id": "TL3JS6F43CKNDQFY",
+    "imp": [
+      {
+        "banner": {
+          "format": [
+            {
+              "h": 600,
+              "w": 300
+            }
+          ],
+          "h": 600,
+          "pos": 1,
+          "w": 300
+        },
+        "ext": {
+          "bidder": {
+            "tagId": "cHJlYmlkLm9yZw"
+          }
+        },
+        "tagid": "example-tag-id",
+        "id": "1",
+        "secure": 1
+      }
+    ],
+    "regs": {
+      "ext": {
+        "gdpr": 0,
+        "us_privacy": "1---"
+      }
+    },
+    "site": {
+      "domain": "www.example.com",
+      "ext": {
+        "amp": 0
+      },
+      "publisher": {
+        "id": "unused_publisher_id"
+      },
+      "page": "https://www.example.com/es6/es6_objects.htm",
+      "ref": "https://www.example.com/es6/es6_objects.htm"
+    },
+    "source": {
+      "ext": {
+        "schain": {
+          "complete": 1,
+          "nodes": [
+            {
+              "asi": "amxrtb.com",
+              "hp": 1,
+              "sid": "1234"
+            }
+          ],
+          "ver": "1.0"
+        }
+      }
+    },
+    "test": 0,
+    "tmax": 300,
+    "user": {
+      "ext": {
+        "eids": [
+          {
+            "source": "amxid",
+            "uids": [
+              {
+                "atype": 1,
+                "id": "88de601e-3d98-48e7-81d7-00000000"
+              }
+            ]
+          },
+          {
+            "source": "adserver.org",
+            "uids": [
+              {
+                "id": "1234567",
+                "ext": {
+                  "rtiPartner": "TDID"
+                }
+              }
+            ]
+          }
+        ],
+        "gdpr": 0,
+        "us_privacy": "1---"
+      }
+    }
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
+      "body": {
+        "device": {
+          "dnt": 0,
+          "h": 1120,
+          "ip": "98.249.0.0",
+          "language": "en",
+          "os": "macos",
+          "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
+          "w": 1792
+        },
+        "id": "TL3JS6F43CKNDQFY",
+        "imp": [
+          {
+            "banner": {
+              "format": [
+                {
+                  "h": 600,
+                  "w": 300
+                }
+              ],
+              "h": 600,
+              "pos": 1,
+              "w": 300
+            },
+            "ext": {
+              "bidder": {
+                "tagId": "cHJlYmlkLm9yZw"
+              }
+            },
+            "tagid": "example-tag-id",
+            "id": "1",
+            "secure": 1
+          }
+        ],
+        "regs": {
+          "ext": {
+            "gdpr": 0,
+            "us_privacy": "1---"
+          }
+        },
+        "site": {
+          "domain": "www.example.com",
+          "ext": {
+            "amp": 0
+          },
+          "publisher": {
+            "id": "cHJlYmlkLm9yZw"
+          },
+          "page": "https://www.example.com/es6/es6_objects.htm",
+          "ref": "https://www.example.com/es6/es6_objects.htm"
+        },
+        "source": {
+          "ext": {
+            "schain": {
+              "complete": 1,
+              "nodes": [
+                {
+                  "asi": "amxrtb.com",
+                  "hp": 1,
+                  "sid": "1234"
+                }
+              ],
+              "ver": "1.0"
+            }
+          }
+        },
+        "tmax": 300,
+        "user": {
+          "ext": {
+            "eids": [
+              {
+                "source": "amxid",
+                "uids": [
+                  {
+                    "atype": 1,
+                    "id": "88de601e-3d98-48e7-81d7-00000000"
+                  }
+                ]
+              },
+              {
+                "source": "adserver.org",
+                "uids": [
+                  {
+                    "id": "1234567",
+                    "ext": {
+                      "rtiPartner": "TDID"
+                    }
+                  }
+                ]
+              }
+            ],
+            "gdpr": 0,
+            "us_privacy": "1---"
+          }
+        }
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "cur": "USD",
+        "id": "os4jorvupkyaa===",
+        "seatbid": [
+          {
+            "bid": [
+              {
+                "adid": "253510977",
+                "adm": "<i></i><script>_c=[];</script><script src=\"https://assets.a-mo.net/js/c.js\"></script>",
+                "adomain": [
+                  "dell.com.au"
+                ],
+                "cid": "668",
+                "crid": "253510977",
+                "ext": {
+                },
+                "h": 250,
+                "id": "8911104898220857797",
+                "impid": "6a362d3a9db4eba300x250",
+                "nurl": "https://1x1.a-mo.net/hbx/bwin",
+                "price": 0.50,
+                "w": 300
+              },
+              {
+                "adid": "253510977",
+                "adm": "<i></i><script>_c=[];</script><script src=\"https://assets.a-mo.net/js/c.js\"></script>",
+                "adomain": [
+                  "dell.com.au"
+                ],
+                "cid": "668",
+                "crid": "253510977",
+                "ext": {},
+                "h": 250,
+                "id": "430444686086263488",
+                "impid": "6a362d3a9db4eba300x250",
+                "nurl": "https://1x1.a-mo.net/hbx/bwin",
+                "price": 0.50,
+                "w": 300
+              }
+            ],
+            "group": 0
+          }
+        ]
+      }
+    }
+  }],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "adid": "253510977",
+            "adm": "<i></i><script>_c=[];</script><script src=\"https://assets.a-mo.net/js/c.js\"></script>",
+            "adomain": [
+              "dell.com.au"
+            ],
+            "cid": "668",
+            "crid": "253510977",
+            "ext": {},
+            "h": 250,
+            "id": "8911104898220857797",
+            "impid": "6a362d3a9db4eba300x250",
+            "nurl": "https://1x1.a-mo.net/hbx/bwin",
+            "price": 0.50,
+            "w": 300
+          },
+          "type": "banner"
+        },
+        {
+          "bid": {
+            "adid": "253510977",
+            "adm": "<i></i><script>_c=[];</script><script src=\"https://assets.a-mo.net/js/c.js\"></script>",
+            "adomain": [
+              "dell.com.au"
+            ],
+            "cid": "668",
+            "crid": "253510977",
+            "ext": {},
+            "h": 250,
+            "id": "430444686086263488",
+            "impid": "6a362d3a9db4eba300x250",
+            "nurl": "https://1x1.a-mo.net/hbx/bwin",
+            "price": 0.50,
+            "w": 300
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/amx/amxtest/exemplary/video-simple.json b/adapters/amx/amxtest/exemplary/video-simple.json
index 8fb3baa2..678722ad 100644
--- a/adapters/amx/amxtest/exemplary/video-simple.json
+++ b/adapters/amx/amxtest/exemplary/video-simple.json
@@ -93,7 +93,7 @@
   },
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0",
+      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
       "body": {
         "device": {
           "dnt": 0,
diff --git a/adapters/amx/amxtest/exemplary/web-simple.json b/adapters/amx/amxtest/exemplary/web-simple.json
index 74854f91..f75bd85a 100644
--- a/adapters/amx/amxtest/exemplary/web-simple.json
+++ b/adapters/amx/amxtest/exemplary/web-simple.json
@@ -98,7 +98,7 @@
   },
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0",
+      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
       "body": {
         "device": {
           "dnt": 0,
diff --git a/adapters/amx/amxtest/supplemental/204-response.json b/adapters/amx/amxtest/supplemental/204-response.json
index 09571a03..f51347d3 100644
--- a/adapters/amx/amxtest/supplemental/204-response.json
+++ b/adapters/amx/amxtest/supplemental/204-response.json
@@ -47,7 +47,7 @@
   },
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0",
+      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
       "body": {
         "device": {
           "dnt": 0,
diff --git a/adapters/amx/amxtest/supplemental/400-response.json b/adapters/amx/amxtest/supplemental/400-response.json
index f10cea89..bb20bc94 100644
--- a/adapters/amx/amxtest/supplemental/400-response.json
+++ b/adapters/amx/amxtest/supplemental/400-response.json
@@ -47,7 +47,7 @@
   },
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0",
+      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
       "body": {
         "device": {
           "dnt": 0,
diff --git a/adapters/amx/amxtest/supplemental/500-response.json b/adapters/amx/amxtest/supplemental/500-response.json
index fe5d8993..c56a217c 100644
--- a/adapters/amx/amxtest/supplemental/500-response.json
+++ b/adapters/amx/amxtest/supplemental/500-response.json
@@ -47,7 +47,7 @@
   },
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.0",
+      "uri": "http://pbs-dev.amxrtb.com/auction/openrtb?v=pbs1.1",
       "body": {
         "device": {
           "dnt": 0,
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/eplanning/eplanningtest/supplemental/banner-2-sizes-in-priority-list-mobile.json b/adapters/eplanning/eplanningtest/supplemental/banner-2-sizes-in-priority-list-mobile.json
new file mode 100644
index 00000000..b1f0677c
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-2-sizes-in-priority-list-mobile.json
@@ -0,0 +1,81 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "devicetype": 4
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 1,
+              "h": 1
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "ci": "12345"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE",
+        "body": {}
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "sI": { "k": "12345" },
+          "sec": "ROS",
+          "sp": [
+            {
+              "k": "300x250",
+              "a": [{
+                "i": "123456789abcdef",
+                "pr": "0.5",
+                "adm": "<div>test</div>",
+                "crid": "abcdef123456789",
+                "id": "adid12345",
+                "w": 300,
+                "h": 250
+              }]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "123456789abcdef",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<div>test</div>",
+            "adid": "adid12345",
+            "crid": "abcdef123456789",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-in-priority-list-desktop.json b/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-in-priority-list-desktop.json
new file mode 100644
index 00000000..31197c38
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-in-priority-list-desktop.json
@@ -0,0 +1,85 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "devicetype": 2
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 970,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            },
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "ci": "12345"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE",
+        "body": {}
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "sI": { "k": "12345" },
+          "sec": "ROS",
+          "sp": [
+            {
+              "k": "300x250",
+              "a": [{
+                "i": "123456789abcdef",
+                "pr": "0.5",
+                "adm": "<div>test</div>",
+                "crid": "abcdef123456789",
+                "id": "adid12345",
+                "w": 300,
+                "h": 250
+              }]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "123456789abcdef",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<div>test</div>",
+            "adid": "adid12345",
+            "crid": "abcdef123456789",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-in-priority-list-mobile.json b/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-in-priority-list-mobile.json
new file mode 100644
index 00000000..661955e8
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-in-priority-list-mobile.json
@@ -0,0 +1,85 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "devicetype": 4
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 1,
+              "h": 1
+            },
+            {
+              "w": 300,
+              "h": 50
+            },
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "ci": "12345"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=320x50%3A320x50&ncb=1&ur=FILE",
+        "body": {}
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "sI": { "k": "12345" },
+          "sec": "ROS",
+          "sp": [
+            {
+              "k": "320x50",
+              "a": [{
+                "i": "123456789abcdef",
+                "pr": "0.5",
+                "adm": "<div>test</div>",
+                "crid": "abcdef123456789",
+                "id": "adid12345",
+                "w": 320,
+                "h": 50
+              }]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "123456789abcdef",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<div>test</div>",
+            "adid": "adid12345",
+            "crid": "abcdef123456789",
+            "w": 320,
+            "h": 50
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-inout-priority-list-mobile.json b/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-inout-priority-list-mobile.json
new file mode 100644
index 00000000..f13df90d
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-3-sizes-inout-priority-list-mobile.json
@@ -0,0 +1,85 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "devicetype": 1
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 970,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 70
+            },
+            {
+              "w": 160,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "ci": "12345"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=970x250%3A970x250&ncb=1&ur=FILE",
+        "body": {}
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "sI": { "k": "12345" },
+          "sec": "ROS",
+          "sp": [
+            {
+              "k": "970x250",
+              "a": [{
+                "i": "123456789abcdef",
+                "pr": "0.5",
+                "adm": "<div>test</div>",
+                "crid": "abcdef123456789",
+                "id": "adid12345",
+                "w": 970,
+                "h": 250
+              }]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "123456789abcdef",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<div>test</div>",
+            "adid": "adid12345",
+            "crid": "abcdef123456789",
+            "w": 970,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/eplanning/eplanningtest/supplemental/banner-4-sizes-in-priority-list-desktop.json b/adapters/eplanning/eplanningtest/supplemental/banner-4-sizes-in-priority-list-desktop.json
new file mode 100644
index 00000000..103be4e4
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/banner-4-sizes-in-priority-list-desktop.json
@@ -0,0 +1,89 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "devicetype": 4
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 1,
+              "h": 1
+            },
+            {
+              "w": 300,
+              "h": 50
+            },
+            {
+              "w": 320,
+              "h": 50
+            },
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "ci": "12345"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=300x250%3A300x250&ncb=1&ur=FILE",
+        "body": {}
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "sI": { "k": "12345" },
+          "sec": "ROS",
+          "sp": [
+            {
+              "k": "300x250",
+              "a": [{
+                "i": "123456789abcdef",
+                "pr": "0.5",
+                "adm": "<div>test</div>",
+                "crid": "abcdef123456789",
+                "id": "adid12345",
+                "w": 300,
+                "h": 250
+              }]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "123456789abcdef",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<div>test</div>",
+            "adid": "adid12345",
+            "crid": "abcdef123456789",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/pulsepoint/params_test.go b/adapters/pulsepoint/params_test.go
new file mode 100644
index 00000000..ac2b314b
--- /dev/null
+++ b/adapters/pulsepoint/params_test.go
@@ -0,0 +1,59 @@
+package pulsepoint
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderPulsepoint, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected pulsepoint params: %s \n Error: %s", validParam, err)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the pubmatic 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.BidderPulsepoint, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected pulsepoint params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"cp":1000, "ct": 2000}`,
+	`{"cp":1001, "ct": 2001}`,
+	`{"cp":1001, "ct": 2001, "cf": "1x1"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"cp":"1000"}`,
+	`{"ct":"1000"}`,
+	`{"cp":1000}`,
+	`{"ct":1000}`,
+	`{"cp":1000, "ct":"1000"}`,
+	`{"cp":1000, "ct": "abcd"}`,
+	`{"cp":"abcd", "ct": 1000}`,
+	`{"cp":"1000.2", "ct": "1000.1"}`,
+}
diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go
index 3eede431..33023d05 100644
--- a/adapters/pulsepoint/pulsepoint_test.go
+++ b/adapters/pulsepoint/pulsepoint_test.go
@@ -1,26 +1,43 @@
 package pulsepoint
 
 import (
+	"encoding/json"
+	"net/http"
+	"testing"
+
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/openrtb_ext"
+
 	"bytes"
 	"context"
-	"encoding/json"
 	"fmt"
 	"io/ioutil"
-	"net/http"
 	"net/http/httptest"
-	"testing"
 	"time"
 
-	"github.com/mxmCherry/openrtb"
-	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adapters/adapterstest"
 	"github.com/prebid/prebid-server/cache/dummycache"
 	"github.com/prebid/prebid-server/config"
-	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/pbs"
 	"github.com/prebid/prebid-server/usersync"
 )
 
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderPulsepoint, config.Adapter{
+		Endpoint: "http://bidder.pulsepoint.com"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "pulsepointtest", bidder)
+}
+
+/////////////////////////////////
+// Legacy implementation: Start
+/////////////////////////////////
+
 /**
  * Verify adapter names are setup correctly.
  */
@@ -75,7 +92,6 @@ func TestPulsePointOpenRTBRequest(t *testing.T) {
 	bidder := req.Bidders[0]
 	adapter := NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, server.URL)
 	adapter.Call(ctx, req, bidder)
-	fmt.Println(service.LastBidRequest)
 	adapterstest.VerifyIntValue(len(service.LastBidRequest.Imp), 1, t)
 	adapterstest.VerifyStringValue(service.LastBidRequest.Imp[0].TagID, "1001", t)
 	adapterstest.VerifyStringValue(service.LastBidRequest.Site.Publisher.ID, "2001", t)
@@ -290,3 +306,7 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService {
 	service.LastBidRequest = &lastBidRequest
 	return service
 }
+
+/////////////////////////////////
+// Legacy implementation: End
+/////////////////////////////////
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json b/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json
new file mode 100644
index 00000000..e99ca648
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/banner-app.json
@@ -0,0 +1,101 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "app": {
+            "bundle": "com.publisher.app",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "app": {
+                    "bundle": "com.publisher.app",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-1",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "banner-1-bid",
+                "impid": "banner-1",
+                "price": 3.5,
+                "adm": "<div>Creative</div>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/banner.json b/adapters/pulsepoint/pulsepointtest/exemplary/banner.json
new file mode 100644
index 00000000..d4cf797d
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/banner.json
@@ -0,0 +1,101 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-1",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "banner-1-bid",
+                "impid": "banner-1",
+                "price": 3.5,
+                "adm": "<div>Creative</div>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json
new file mode 100644
index 00000000..6e16f997
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-app.json
@@ -0,0 +1,96 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "app": {
+            "bundle": "com.publisher.app"
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "app": {
+                    "bundle": "com.publisher.app",
+                    "publisher": {
+                        "id": "1234"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-1",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "banner-1-bid",
+                "impid": "banner-1",
+                "price": 3.5,
+                "adm": "<div>Creative</div>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json
new file mode 100644
index 00000000..6d658a24
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/empty-pub-node-site.json
@@ -0,0 +1,96 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html"
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-1",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "banner-1-bid",
+                "impid": "banner-1",
+                "price": 3.5,
+                "adm": "<div>Creative</div>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json b/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json
new file mode 100644
index 00000000..1a103553
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/multi-imps.json
@@ -0,0 +1,151 @@
+{
+    "mockBidRequest": {
+        "id": "some-request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }, {
+            "id": "video-1",
+            "video": {
+                "w": 400,
+                "h": 300,
+                "mimes": ["video/x-flv"],
+                "minduration": 20
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 2001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "some-request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }, {
+                    "id": "video-1",
+                    "tagid": "2001",
+                    "video": {
+                        "w": 400,
+                        "h": 300,
+                        "mimes": ["video/x-flv"],
+                        "minduration": 20
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 2001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-1",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }, {
+                        "id": "video-1-bid",
+                        "impid": "video-1",
+                        "price": 4.5,
+                        "adm": "<vast>Creative</vast>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "201"
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "banner-1-bid",
+                "impid": "banner-1",
+                "price": 3.5,
+                "adm": "<div>Creative</div>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }, {
+            "bid": {
+                "id": "video-1-bid",
+                "impid": "video-1",
+                "price": 4.5,
+                "adm": "<vast>Creative</vast>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "201"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/native.json b/adapters/pulsepoint/pulsepointtest/exemplary/native.json
new file mode 100644
index 00000000..72c8532d
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/native.json
@@ -0,0 +1,97 @@
+{
+    "mockBidRequest": {
+        "id": "some-request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "native-1",
+            "native": {
+                "ver": "1.0",
+                "request": "{\"layout\":501,\"adunit\":501,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":26}},{\"id\":2,\"required\":0,\"data\":{\"type\":2,\"len\":90}}]}"
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 2001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "some-request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "native-1",
+                    "tagid": "2001",
+                    "native": {
+                        "ver": "1.0",
+                        "request": "{\"layout\":501,\"adunit\":501,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":26}},{\"id\":2,\"required\":0,\"data\":{\"type\":2,\"len\":90}}]}"
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 2001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "native-1-bid",
+                        "impid": "native-1",
+                        "price": 3.5,
+                        "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Adv:\"}},{\"data\":{\"type\":2,\"value\":\"Teeth Whitening\"},\"id\":2}],\"imptrackers\":[\"https://tracker.pulsepoint.com//\"],\"link\":{\"url\":\"http://click.pulsepoint.com/\"},\"ver\":\"1.0\"}}",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20"
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "native-1-bid",
+                "impid": "native-1",
+                "price": 3.5,
+                "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Adv:\"}},{\"data\":{\"type\":2,\"value\":\"Teeth Whitening\"},\"id\":2}],\"imptrackers\":[\"https://tracker.pulsepoint.com//\"],\"link\":{\"url\":\"http://click.pulsepoint.com/\"},\"ver\":\"1.0\"}}",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20"
+            },
+            "type": "native"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/exemplary/video.json b/adapters/pulsepoint/pulsepointtest/exemplary/video.json
new file mode 100644
index 00000000..980f49f2
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/exemplary/video.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+        "id": "some-request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "video-1",
+            "video": {
+                "w": 400,
+                "h": 300,
+                "mimes": ["video/x-flv"],
+                "minduration": 20
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 2001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "some-request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "video-1",
+                    "tagid": "2001",
+                    "video": {
+                        "w": 400,
+                        "h": 300,
+                        "mimes": ["video/x-flv"],
+                        "minduration": 20
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 2001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "video-1-bid",
+                        "impid": "video-1",
+                        "price": 3.5,
+                        "adm": "<vast>Creative</vast>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": [{
+            "bid": {
+                "id": "video-1-bid",
+                "impid": "video-1",
+                "price": 3.5,
+                "adm": "<vast>Creative</vast>",
+                "adomain": [
+                    "advertiser.com"
+                ],
+                "crid": "20",
+                "w": 300,
+                "h": 250
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/params/race/banner.json b/adapters/pulsepoint/pulsepointtest/params/race/banner.json
index 1f2e34df..bc3e9371 100644
--- a/adapters/pulsepoint/pulsepointtest/params/race/banner.json
+++ b/adapters/pulsepoint/pulsepointtest/params/race/banner.json
@@ -1,5 +1,4 @@
 {
-  "cf": "300X250",
   "cp": 512379,
   "ct": 486653
 }
diff --git a/adapters/pulsepoint/pulsepointtest/params/race/native.json b/adapters/pulsepoint/pulsepointtest/params/race/native.json
new file mode 100644
index 00000000..57867bc2
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/params/race/native.json
@@ -0,0 +1,4 @@
+{
+  "cp": 512379,
+  "ct": 486655
+}
diff --git a/adapters/pulsepoint/pulsepointtest/params/race/video.json b/adapters/pulsepoint/pulsepointtest/params/race/video.json
new file mode 100644
index 00000000..d8ef37eb
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+  "cp": 512379,
+  "ct": 486654
+}
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json
new file mode 100644
index 00000000..b5209ed4
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json
@@ -0,0 +1,90 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-2",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": "300",
+                        "h": "250"
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": [{
+          "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type uint64",
+          "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json
new file mode 100644
index 00000000..3fe1422b
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-input.json
@@ -0,0 +1,70 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 400
+        }
+    }],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": [{
+        "value": "Bad user input: HTTP status 400",
+        "comparison": "literal"
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json
new file mode 100644
index 00000000..2d0c74d3
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid-params.json
@@ -0,0 +1,32 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": "1234",
+                    "ct": "1001"
+                }
+            }
+        }]
+    },
+    "httpCalls": [],
+    "expectedMakeRequestsErrors": [{
+        "value": "json: cannot unmarshal string into Go struct field ExtImpPulsePoint.cp of type int",
+        "comparison": "literal"
+    }],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json
new file mode 100644
index 00000000..7de9bd3c
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-prebid.ext.json
@@ -0,0 +1,27 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": ""
+        }]
+    },
+    "httpCalls": [],
+    "expectedMakeRequestsErrors": [{
+        "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder",
+        "comparison": "literal"
+    }],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/error.json b/adapters/pulsepoint/pulsepointtest/supplemental/error.json
new file mode 100644
index 00000000..c469e3bc
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/error.json
@@ -0,0 +1,70 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 503
+        }
+    }],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": [{
+        "value": "Bad server response: HTTP status 503",
+        "comparison": "literal"
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json b/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json
new file mode 100644
index 00000000..30b57e1e
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/impid-mismatch.json
@@ -0,0 +1,87 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "response-id",
+                "seatbid": [{
+                    "bid": [{
+                        "id": "banner-1-bid",
+                        "impid": "banner-2",
+                        "price": 3.5,
+                        "adm": "<div>Creative</div>",
+                        "adomain": [
+                            "advertiser.com"
+                        ],
+                        "crid": "20",
+                        "w": 300,
+                        "h": 250
+                    }],
+                    "seat": "pulsepoint-seat"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [{
+        "bids": []
+    }]
+}
\ No newline at end of file
diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/passback.json b/adapters/pulsepoint/pulsepointtest/supplemental/passback.json
new file mode 100644
index 00000000..434f5a7a
--- /dev/null
+++ b/adapters/pulsepoint/pulsepointtest/supplemental/passback.json
@@ -0,0 +1,68 @@
+{
+    "mockBidRequest": {
+        "id": "request-id",
+        "site": {
+            "page": "http://publisher.com/index.html",
+            "publisher": {
+                "id": "123456789",
+                "name": "publisher.com"
+            }
+        },
+        "imp": [{
+            "id": "banner-1",
+            "banner": {
+                "w": 320,
+                "h": 50
+            },
+            "ext": {
+                "bidder": {
+                    "cp": 1234,
+                    "ct": 1001
+                }
+            }
+        }]
+    },
+    "httpCalls": [{
+        "expectedRequest": {
+            "headers": {
+                "Content-Type": [
+                    "application/json;charset=utf-8"
+                ],
+                "Accept": [
+                    "application/json"
+                ]
+            },
+            "uri": "http://bidder.pulsepoint.com",
+            "body": {
+                "id": "request-id",
+                "site": {
+                    "page": "http://publisher.com/index.html",
+                    "publisher": {
+                        "id": "1234",
+                        "name": "publisher.com"
+                    }
+                },
+                "imp": [{
+                    "id": "banner-1",
+                    "tagid": "1001",
+                    "banner": {
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext": {
+                        "bidder": {
+                            "cp": 1234,
+                            "ct": 1001
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 204,
+            "body": {}
+        }
+    }],
+    "expectedBidResponses": [],
+    "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/config/config_test.go b/config/config_test.go
index 57631395..7ff5f0fa 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -527,12 +527,6 @@ func TestNegativeRequestSize(t *testing.T) {
 	assertOneError(t, cfg.validate(), "cfg.max_request_size must be >= 0. Got -1")
 }
 
-func TestNegativeVendorID(t *testing.T) {
-	cfg := newDefaultConfig(t)
-	cfg.GDPR.HostVendorID = -1
-	assertOneError(t, cfg.validate(), "gdpr.host_vendor_id must be in the range [0, 65535]. Got -1")
-}
-
 func TestNegativePrometheusTimeout(t *testing.T) {
 	cfg := newDefaultConfig(t)
 	cfg.Metrics.Prometheus.Port = 8001
@@ -540,10 +534,44 @@ func TestNegativePrometheusTimeout(t *testing.T) {
 	assertOneError(t, cfg.validate(), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001")
 }
 
-func TestOverflowedVendorID(t *testing.T) {
+func TestInvalidHostVendorID(t *testing.T) {
+	tests := []struct {
+		description  string
+		vendorID     int
+		wantErrorMsg string
+	}{
+		{
+			description:  "Negative GDPR.HostVendorID",
+			vendorID:     -1,
+			wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got -1",
+		},
+		{
+			description:  "Overflowed GDPR.HostVendorID",
+			vendorID:     (0xffff) + 1,
+			wantErrorMsg: "gdpr.host_vendor_id must be in the range [0, 65535]. Got 65536",
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := newDefaultConfig(t)
+		cfg.GDPR.HostVendorID = tt.vendorID
+		errs := cfg.validate()
+
+		assert.Equal(t, 1, len(errs), tt.description)
+		assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description)
+	}
+}
+
+func TestInvalidFetchGVL(t *testing.T) {
+	cfg := newDefaultConfig(t)
+	cfg.GDPR.TCF1.FetchGVL = true
+	assertOneError(t, cfg.validate(), "gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward")
+}
+
+func TestInvalidAMPException(t *testing.T) {
 	cfg := newDefaultConfig(t)
-	cfg.GDPR.HostVendorID = (0xffff) + 1
-	assertOneError(t, cfg.validate(), "gdpr.host_vendor_id must be in the range [0, 65535]. Got 65536")
+	cfg.GDPR.AMPException = true
+	assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")
 }
 
 func TestNegativeCurrencyConverterFetchInterval(t *testing.T) {
diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go
index 6a237e39..db26a84b 100644
--- a/endpoints/auction_test.go
+++ b/endpoints/auction_test.go
@@ -349,7 +349,7 @@ func TestCacheVideoOnly(t *testing.T) {
 		t.Fatal(err.Error())
 	}
 	syncers := usersyncers.NewSyncerMap(cfg)
-	gdprPerms := gdpr.NewPermissions(nil, config.GDPR{
+	gdprPerms := gdpr.NewPermissions(context.Background(), config.GDPR{
 		HostVendorID: 0,
 	}, nil, nil)
 	prebid_cache_client.InitPrebidCache(server.URL)
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index d7059648..92eb0129 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -10,7 +10,6 @@ import (
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adapters/appnexus"
 	"github.com/prebid/prebid-server/adapters/lifestreet"
-	"github.com/prebid/prebid-server/adapters/pulsepoint"
 	"github.com/prebid/prebid-server/adapters/rubicon"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
@@ -41,7 +40,7 @@ func TestBuildAdaptersSuccess(t *testing.T) {
 
 	appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
 	appnexusBidderWithInfo := adapters.EnforceBidderInfo(appnexusBidder, infoActive)
-	appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus)
+	appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
 	appnexusBidderValidated := addValidatedBidderMiddleware(appnexusBidderAdapted)
 
 	idLegacyAdapted := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")}
@@ -78,11 +77,11 @@ func TestBuildExchangeBidders(t *testing.T) {
 
 	appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
 	appnexusBidderWithInfo := adapters.EnforceBidderInfo(appnexusBidder, infoActive)
-	appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus)
+	appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
 
 	rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{})
 	rubiconBidderWithInfo := adapters.EnforceBidderInfo(rubiconBidder, infoActive)
-	rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon)
+	rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil)
 
 	testCases := []struct {
 		description     string
@@ -214,9 +213,9 @@ func TestBuildBidders(t *testing.T) {
 		},
 		{
 			description:   "Success - Ignores Legacy",
-			adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}, "pulsepoint": {}},
-			bidderInfos:   map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive, "pulsepoint": infoActive},
-			builders:      map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder, openrtb_ext.BidderPulsepoint: inconsequentialBuilder},
+			adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}},
+			bidderInfos:   map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive},
+			builders:      map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder},
 			expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{
 				openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive),
 			},
@@ -267,7 +266,6 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) {
 	cfg := config.Adapter{Endpoint: "anyEndpoint"}
 
 	expectedLifestreet := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")}
-	expectedPulsepoint := &adaptedAdapter{pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")}
 
 	testCases := []struct {
 		description   string
@@ -276,29 +274,23 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) {
 		expected      map[openrtb_ext.BidderName]adaptedBidder
 	}{
 		{
-			description:   "All Active",
-			adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg},
-			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoActive, "pulsepoint": infoActive},
-			expected:      map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet, "pulsepoint": expectedPulsepoint},
+			description:   "Active",
+			adapterConfig: map[string]config.Adapter{"lifestreet": cfg},
+			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoActive},
+			expected:      map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet},
 		},
 		{
-			description:   "All Disabled",
-			adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg},
-			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoDisabled, "pulsepoint": infoDisabled},
+			description:   "Disabled",
+			adapterConfig: map[string]config.Adapter{"lifestreet": cfg},
+			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoDisabled},
 			expected:      map[openrtb_ext.BidderName]adaptedBidder{},
 		},
 		{
-			description:   "All Unknown",
-			adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg},
-			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoUnknown, "pulsepoint": infoUnknown},
+			description:   "Unknown",
+			adapterConfig: map[string]config.Adapter{"lifestreet": cfg},
+			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoUnknown},
 			expected:      map[openrtb_ext.BidderName]adaptedBidder{},
 		},
-		{
-			description:   "Mixed",
-			adapterConfig: map[string]config.Adapter{"lifestreet": cfg, "pulsepoint": cfg},
-			bidderInfos:   map[string]adapters.BidderInfo{"lifestreet": infoActive, "pulsepoint": infoUnknown},
-			expected:      map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet},
-		},
 	}
 
 	for _, test := range testCases {
@@ -405,7 +397,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 
 type fakeAdaptedBidder struct{}
 
-func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
+func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
 	return nil, nil
 }
 
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 8266daaa..b809c7e0 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -35,6 +35,16 @@ import (
 // 1. The Bidder implementation is called with the arguments we expect.
 // 2. The returned values are correct for a non-test bid.
 func TestSingleBidder(t *testing.T) {
+	type aTest struct {
+		debugInfo    *adapters.DebugInfo
+		httpCallsLen int
+	}
+
+	testCases := []*aTest{
+		{&adapters.DebugInfo{Allow: false}, 0},
+		{&adapters.DebugInfo{Allow: true}, 1},
+	}
+
 	respStatus := 200
 	respBody := "{\"bid\":false}"
 	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
@@ -46,24 +56,6 @@ func TestSingleBidder(t *testing.T) {
 	bidAdjustment := 2.0
 	firstInitialPrice := 3.0
 	secondInitialPrice := 4.0
-	mockBidderResponse := &adapters.BidderResponse{
-		Bids: []*adapters.TypedBid{
-			{
-				Bid: &openrtb.Bid{
-					Price: firstInitialPrice,
-				},
-				BidType:      openrtb_ext.BidTypeBanner,
-				DealPriority: 4,
-			},
-			{
-				Bid: &openrtb.Bid{
-					Price: secondInitialPrice,
-				},
-				BidType:      openrtb_ext.BidTypeVideo,
-				DealPriority: 5,
-			},
-		},
-	}
 
 	bidderImpl := &goodSingleBidder{
 		httpRequest: &adapters.RequestData{
@@ -72,53 +64,81 @@ func TestSingleBidder(t *testing.T) {
 			Body:    []byte("{\"key\":\"val\"}"),
 			Headers: http.Header{},
 		},
-		bidResponse: mockBidderResponse,
+		bidResponse: nil,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
-	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
 
-	// Make sure the goodSingleBidder was called with the expected arguments.
-	if bidderImpl.httpResponse == nil {
-		t.Errorf("The Bidder should be called with the server's response.")
-	}
-	if bidderImpl.httpResponse.StatusCode != respStatus {
-		t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode)
-	}
-	if string(bidderImpl.httpResponse.Body) != respBody {
-		t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body))
-	}
+	ctx := context.Background()
+	ctx = context.WithValue(ctx, DebugContextKey, true)
 
-	// Make sure the returned values are what we expect
-	if len(errs) != 0 {
-		t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
-	}
-	if len(seatBid.bids) != len(mockBidderResponse.Bids) {
-		t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
-	}
-	for index, typedBid := range mockBidderResponse.Bids {
-		if typedBid.Bid != seatBid.bids[index].bid {
-			t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
+	for _, test := range testCases {
+
+		mockBidderResponse := &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb.Bid{
+						Price: firstInitialPrice,
+					},
+					BidType:      openrtb_ext.BidTypeBanner,
+					DealPriority: 4,
+				},
+				{
+					Bid: &openrtb.Bid{
+						Price: secondInitialPrice,
+					},
+					BidType:      openrtb_ext.BidTypeVideo,
+					DealPriority: 5,
+				},
+			},
 		}
-		if typedBid.BidType != seatBid.bids[index].bidType {
-			t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+		bidderImpl.bidResponse = mockBidderResponse
+
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
+		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+
+		seatBid, errs := bidder.requestBid(ctx, &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+
+		// Make sure the goodSingleBidder was called with the expected arguments.
+		if bidderImpl.httpResponse == nil {
+			t.Errorf("The Bidder should be called with the server's response.")
 		}
-		if typedBid.DealPriority != seatBid.bids[index].dealPriority {
-			t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+		if bidderImpl.httpResponse.StatusCode != respStatus {
+			t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode)
+		}
+		if string(bidderImpl.httpResponse.Body) != respBody {
+			t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body))
+		}
+
+		// Make sure the returned values are what we expect
+		if len(errs) != 0 {
+			t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
+		}
+		if len(seatBid.bids) != len(mockBidderResponse.Bids) {
+			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
+		}
+		for index, typedBid := range mockBidderResponse.Bids {
+			if typedBid.Bid != seatBid.bids[index].bid {
+				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
+			}
+			if typedBid.BidType != seatBid.bids[index].bidType {
+				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			}
+			if typedBid.DealPriority != seatBid.bids[index].dealPriority {
+				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.bids[index].bidType)
+			}
+		}
+		if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
+			t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
+		}
+		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
+			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
+		}
+		if len(seatBid.httpCalls) != test.httpCallsLen {
+			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls))
 		}
-	}
-	if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
-		t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
-	}
-	if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
-		t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
-	}
-	if len(seatBid.httpCalls) != 0 {
-		t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls))
-	}
 
-	if len(seatBid.ext) != 0 {
-		t.Errorf("The bidder shouldn't define any seatBid.ext. Got %s", string(seatBid.ext))
+		if len(seatBid.ext) != 0 {
+			t.Errorf("The bidder shouldn't define any seatBid.ext. Got %s", string(seatBid.ext))
+		}
 	}
 }
 
@@ -162,9 +182,9 @@ func TestMultiBidder(t *testing.T) {
 			}},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 
 	if seatBid == nil {
 		t.Fatalf("SeatBid should exist, because bids exist.")
@@ -524,7 +544,7 @@ func TestMultiCurrencies(t *testing.T) {
 		)
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -540,6 +560,7 @@ func TestMultiCurrencies(t *testing.T) {
 			1,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
+			true,
 		)
 
 		// Verify:
@@ -675,7 +696,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 		seatBid, errs := bidder.requestBid(
 			context.Background(),
@@ -684,6 +705,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 			1,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
+			true,
 		)
 
 		// Verify:
@@ -841,7 +863,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -856,6 +878,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 			1,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
+			true,
 		)
 
 		// Verify:
@@ -886,6 +909,9 @@ func TestBadRequestLogging(t *testing.T) {
 	if ext.Status != 0 {
 		t.Errorf("The Status code should be 0. Got %d", ext.Status)
 	}
+	if len(ext.RequestHeaders) > 0 {
+		t.Errorf("The request headers should be empty. Got %s", ext.RequestHeaders)
+	}
 }
 
 // TestBadResponseLogging makes sure that openrtb_ext works properly if we don't get a sensible HTTP response.
@@ -894,6 +920,9 @@ func TestBadResponseLogging(t *testing.T) {
 		request: &adapters.RequestData{
 			Uri:  "test.com",
 			Body: []byte("request body"),
+			Headers: http.Header{
+				"header-1": []string{"value-1"},
+			},
 		},
 		err: errors.New("Bad response"),
 	}
@@ -910,6 +939,7 @@ func TestBadResponseLogging(t *testing.T) {
 	if ext.Status != 0 {
 		t.Errorf("The Status code should be 0. Got %d", ext.Status)
 	}
+	assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"header-1:value-1\"")
 }
 
 // TestSuccessfulResponseLogging makes sure that openrtb_ext works properly if the HTTP request is successful.
@@ -918,6 +948,9 @@ func TestSuccessfulResponseLogging(t *testing.T) {
 		request: &adapters.RequestData{
 			Uri:  "test.com",
 			Body: []byte("request body"),
+			Headers: http.Header{
+				"header-1": []string{"value-1", "value-2"},
+			},
 		},
 		response: &adapters.ResponseData{
 			StatusCode: 200,
@@ -937,6 +970,7 @@ func TestSuccessfulResponseLogging(t *testing.T) {
 	if ext.Status != info.response.StatusCode {
 		t.Errorf("The Status code should be 0. Got %d", ext.Status)
 	}
+	assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"%s\". Got %s", info.request.Headers, ext.RequestHeaders)
 }
 
 func TestMobileNativeTypes(t *testing.T) {
@@ -1020,7 +1054,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			bidResponse: tc.mockBidderResponse,
 		}
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		seatBids, _ := bidder.requestBid(
@@ -1030,6 +1064,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			1.0,
 			currencyConverter.Rates(),
 			&adapters.ExtraRequestInfo{},
+			true,
 		)
 
 		var actualValue string
@@ -1041,9 +1076,9 @@ func TestMobileNativeTypes(t *testing.T) {
 }
 
 func TestErrorReporting(t *testing.T) {
-	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 	if bids != nil {
 		t.Errorf("There should be no seatbid if no http requests are returned.")
 	}
@@ -1224,9 +1259,9 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 	metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
 
 	// Run requestBid using an http.Client with a mock handler
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus)
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	_, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	_, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
@@ -1401,6 +1436,23 @@ func TestTimeoutNotificationOn(t *testing.T) {
 	assert.EqualValues(t, logExpected, logActual)
 }
 
+func TestParseDebugInfoTrue(t *testing.T) {
+	debugInfo := &adapters.DebugInfo{Allow: true}
+	resDebugInfo := parseDebugInfo(debugInfo)
+	assert.True(t, resDebugInfo, "Debug Allow value should be true")
+}
+
+func TestParseDebugInfoFalse(t *testing.T) {
+	debugInfo := &adapters.DebugInfo{Allow: false}
+	resDebugInfo := parseDebugInfo(debugInfo)
+	assert.False(t, resDebugInfo, "Debug Allow value should be false")
+}
+
+func TestParseDebugInfoIsNil(t *testing.T) {
+	resDebugInfo := parseDebugInfo(nil)
+	assert.True(t, resDebugInfo, "Debug Allow value should be true")
+}
+
 func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder {
 	bidderInfo := adapters.BidderInfo{
 		Status: adapters.StatusActive,
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index bf35b2db..e7fc0b04 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) {
 			},
 		},
 	})
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{})
+	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
 	assert.Len(t, seatBid.bids, 3)
 	assert.Len(t, errs, 0)
 }
@@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) {
 			},
 		},
 	})
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{})
+	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
 	assert.Len(t, seatBid.bids, 0)
 	assert.Len(t, errs, 5)
 }
@@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) {
 			},
 		},
 	})
-	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{})
+	seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
 	assert.Len(t, seatBid.bids, 2)
 	assert.Len(t, errs, 3)
 }
@@ -246,7 +246,7 @@ func TestCurrencyBids(t *testing.T) {
 			Cur: tc.brqCur,
 		}
 
-		seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{})
+		seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true)
 		assert.Len(t, seatBid.bids, expectedValidBids)
 		assert.Len(t, errs, expectedErrs)
 	}
@@ -257,6 +257,6 @@ type mockAdaptedBidder struct {
 	errorResponse []error
 }
 
-func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (*pbsOrtbSeatBid, []error) {
+func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
 	return b.bidResponse, b.errorResponse
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 416875a1..8bdf6c45 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -165,41 +165,66 @@ func TestDebugBehaviour(t *testing.T) {
 	type outTest struct {
 		debugInfoIncluded bool
 	}
+
+	type debugData struct {
+		bidderLevelDebugAllowed  bool
+		accountLevelDebugAllowed bool
+	}
+
 	type aTest struct {
-		desc string
-		in   inTest
-		out  outTest
+		desc      string
+		in        inTest
+		out       outTest
+		debugData debugData
 	}
 	testCases := []aTest{
 		{
-			desc: "test flag equals zero, ext debug flag false, no debug info expected",
-			in:   inTest{test: 0, debug: false},
-			out:  outTest{debugInfoIncluded: false},
+			desc:      "test flag equals zero, ext debug flag false, no debug info expected",
+			in:        inTest{test: 0, debug: false},
+			out:       outTest{debugInfoIncluded: false},
+			debugData: debugData{true, true},
 		},
 		{
-			desc: "test flag equals zero, ext debug flag true, debug info expected",
-			in:   inTest{test: 0, debug: true},
-			out:  outTest{debugInfoIncluded: true},
+			desc:      "test flag equals zero, ext debug flag true, debug info expected",
+			in:        inTest{test: 0, debug: true},
+			out:       outTest{debugInfoIncluded: true},
+			debugData: debugData{true, true},
 		},
 		{
-			desc: "test flag equals 1, ext debug flag false, debug info expected",
-			in:   inTest{test: 1, debug: false},
-			out:  outTest{debugInfoIncluded: true},
+			desc:      "test flag equals 1, ext debug flag false, debug info expected",
+			in:        inTest{test: 1, debug: false},
+			out:       outTest{debugInfoIncluded: true},
+			debugData: debugData{true, true},
 		},
 		{
-			desc: "test flag equals 1, ext debug flag true, debug info expected",
-			in:   inTest{test: 1, debug: true},
-			out:  outTest{debugInfoIncluded: true},
+			desc:      "test flag equals 1, ext debug flag true, debug info expected",
+			in:        inTest{test: 1, debug: true},
+			out:       outTest{debugInfoIncluded: true},
+			debugData: debugData{true, true},
 		},
 		{
-			desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected",
-			in:   inTest{test: 2, debug: false},
-			out:  outTest{debugInfoIncluded: false},
+			desc:      "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected",
+			in:        inTest{test: 2, debug: false},
+			out:       outTest{debugInfoIncluded: false},
+			debugData: debugData{true, true},
 		},
 		{
-			desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected",
-			in:   inTest{test: -1, debug: true},
-			out:  outTest{debugInfoIncluded: true},
+			desc:      "test flag not equal to 0 nor 1, ext debug flag true, debug info expected",
+			in:        inTest{test: -1, debug: true},
+			out:       outTest{debugInfoIncluded: true},
+			debugData: debugData{true, true},
+		},
+		{
+			desc:      "test account level debug disabled",
+			in:        inTest{test: -1, debug: true},
+			out:       outTest{debugInfoIncluded: false},
+			debugData: debugData{true, false},
+		},
+		{
+			desc:      "test bidder level debug disabled",
+			in:        inTest{test: -1, debug: true},
+			out:       outTest{debugInfoIncluded: false},
+			debugData: debugData{false, true},
 		},
 	}
 
@@ -239,17 +264,25 @@ func TestDebugBehaviour(t *testing.T) {
 	}
 
 	e := new(exchange)
-	e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus),
-	}
+
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.DummyMetricsEngine{}
 	e.gDPR = gdpr.AlwaysAllow{}
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
 
+	ctx := context.Background()
+
 	// Run tests
 	for _, test := range testCases {
+
+		e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
+			openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}),
+		}
+
+		//request level debug key
+		ctx = context.WithValue(ctx, DebugContextKey, test.in.debug)
+
 		bidRequest.Test = test.in.test
 
 		if test.in.debug {
@@ -260,13 +293,13 @@ func TestDebugBehaviour(t *testing.T) {
 
 		auctionRequest := AuctionRequest{
 			BidRequest: bidRequest,
-			Account:    config.Account{},
+			Account:    config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed},
 			UserSyncs:  &emptyUsersync{},
 			StartTime:  time.Now(),
 		}
 
 		// Run test
-		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, nil)
+		outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil)
 
 		// Assert no HoldAuction error
 		assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err)
@@ -292,8 +325,132 @@ func TestDebugBehaviour(t *testing.T) {
 			if test.in.debug {
 				diffJson(t, test.desc, bidRequest.Ext, actualExt.Debug.ResolvedRequest.Ext)
 			}
+		} else if !test.debugData.bidderLevelDebugAllowed && test.debugData.accountLevelDebugAllowed {
+			assert.Equal(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty")
+
+		} else {
+			assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty")
+		}
+	}
+}
+
+func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
+
+	type testCase struct {
+		bidder1DebugEnabled bool
+		bidder2DebugEnabled bool
+	}
+
+	testCases := []testCase{
+		{
+			bidder1DebugEnabled: true, bidder2DebugEnabled: true,
+		},
+		{
+			bidder1DebugEnabled: true, bidder2DebugEnabled: false,
+		},
+		{
+			bidder1DebugEnabled: false, bidder2DebugEnabled: true,
+		},
+		{
+			bidder1DebugEnabled: false, bidder2DebugEnabled: false,
+		},
+	}
+
+	// Set up test
+	noBidServer := func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(204)
+	}
+	server := httptest.NewServer(http.HandlerFunc(noBidServer))
+	defer server.Close()
+
+	categoriesFetcher, err := newCategoryFetcher("./test/category-mapping")
+	if err != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", err)
+	}
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{},
+	}
+
+	e := new(exchange)
+	e.cache = &wellBehavedCache{}
+	e.me = &metricsConf.DummyMetricsEngine{}
+	e.gDPR = gdpr.AlwaysAllow{}
+	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e.categoriesFetcher = categoriesFetcher
+
+	debugLog := DebugLog{}
+
+	for _, testCase := range testCases {
+		bidRequest := &openrtb.BidRequest{
+			ID: "some-request-id",
+			Imp: []openrtb.Imp{{
+				ID:     "some-impression-id",
+				Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
+				Ext:    json.RawMessage(`{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}`),
+			}},
+			Site:   &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
+			Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"},
+			AT:     1,
+			TMax:   500,
+		}
+
+		bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`)
+
+		auctionRequest := AuctionRequest{
+			BidRequest: bidRequest,
+			Account:    config.Account{DebugAllow: true},
+			UserSyncs:  &emptyUsersync{},
+			StartTime:  time.Now(),
+		}
+
+		e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
+			openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: testCase.bidder1DebugEnabled}),
+			openrtb_ext.BidderTelaria:  adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: testCase.bidder2DebugEnabled}),
+		}
+		// Run test
+		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
+		// Assert no HoldAuction err
+		assert.NoErrorf(t, err, "ex.HoldAuction returned an err")
+		assert.NotNilf(t, outBidResponse.Ext, "outBidResponse.Ext should not be nil")
+
+		actualExt := &openrtb_ext.ExtBidResponse{}
+		err = json.Unmarshal(outBidResponse.Ext, actualExt)
+		assert.NoErrorf(t, err, "JSON field unmarshaling err. ")
+
+		assert.NotEmpty(t, actualExt.Prebid, "ext.prebid should not be empty")
+		assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set")
+		assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "ext.prebid.auctiontimestamp has incorrect value")
+
+		assert.NotNilf(t, actualExt, "ext.debug field is expected to be included in this outBidResponse.Ext and not be nil")
+
+		// Assert "Debug fields
+		if testCase.bidder1DebugEnabled {
+			assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["appnexus"][0].Uri, "Url for bidder with debug enabled is incorrect")
+			assert.NotNilf(t, actualExt.Debug.HttpCalls["appnexus"][0].RequestBody, "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
+		}
+		if testCase.bidder2DebugEnabled {
+			assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["telaria"][0].Uri, "Url for bidder with debug enabled is incorrect")
+			assert.NotNilf(t, actualExt.Debug.HttpCalls["telaria"][0].RequestBody, "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
+		}
+		if !testCase.bidder1DebugEnabled {
+			assert.Nil(t, actualExt.Debug.HttpCalls["appnexus"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
+		}
+		if !testCase.bidder2DebugEnabled {
+			assert.Nil(t, actualExt.Debug.HttpCalls["telaria"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
+		}
+
+		if testCase.bidder1DebugEnabled && testCase.bidder2DebugEnabled {
+			assert.Equal(t, 2, len(actualExt.Debug.HttpCalls), "With bidder level debug enable option for both bidders http calls should have 2 elements")
 		}
 	}
+
 }
 
 func TestReturnCreativeEndToEnd(t *testing.T) {
@@ -434,7 +591,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 
 	e := new(exchange)
 	e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus),
+		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.DummyMetricsEngine{}
@@ -465,7 +622,8 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 			}
 
 			// Run test
-			outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, nil)
+			debugLog := DebugLog{}
+			outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
 
 			// Assert return error, if any
 			if testGroup.expectError {
@@ -716,7 +874,7 @@ func TestBidReturnsCreative(t *testing.T) {
 	}
 	e := new(exchange)
 	e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus),
+		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.DummyMetricsEngine{}
@@ -1059,8 +1217,9 @@ func TestRaceIntegration(t *testing.T) {
 		UserSyncs:  &emptyUsersync{},
 	}
 
+	debugLog := DebugLog{}
 	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange)
-	_, err := ex.HoldAuction(context.Background(), auctionRequest, nil)
+	_, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -1279,8 +1438,8 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 		Account:    config.Account{},
 		UserSyncs:  &emptyUsersync{},
 	}
-
-	_, err := e.HoldAuction(context.Background(), auctionRequest, nil)
+	debugLog := DebugLog{}
+	_, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
 	}
@@ -1406,14 +1565,17 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 		Account: config.Account{
 			ID:            "testaccount",
 			EventsEnabled: spec.EventsEnabled,
+			DebugAllow:    true,
 		},
 		UserSyncs: mockIdFetcher(spec.IncomingRequest.Usersyncs),
 	}
 	if spec.StartTime > 0 {
 		auctionRequest.StartTime = time.Unix(0, spec.StartTime*1e+6)
 	}
+	ctx := context.Background()
+	ctx = context.WithValue(ctx, DebugContextKey, true)
 
-	bid, err := ex.HoldAuction(context.Background(), auctionRequest, debugLog)
+	bid, err := ex.HoldAuction(ctx, auctionRequest, debugLog)
 	responseTimes := extractResponseTimes(t, filename, bid)
 	for _, bidderName := range biddersInAuction {
 		if _, ok := responseTimes[bidderName]; !ok {
@@ -2622,7 +2784,7 @@ type validatingBidder struct {
 	mockResponses map[string]bidderResponse
 }
 
-func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (seatBid *pbsOrtbSeatBid, errs []error) {
+func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) {
 	if expectedRequest, ok := b.expectations[string(name)]; ok {
 		if expectedRequest != nil {
 			if expectedRequest.BidAdjustment != bidAdjustment {
@@ -2801,7 +2963,7 @@ func (e *mockUsersync) LiveSyncCount() int {
 
 type panicingAdapter struct{}
 
-func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo) (posb *pbsOrtbSeatBid, errs []error) {
+func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }
 
diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json
index 1247b9f0..507aee1a 100644
--- a/exchange/exchangetest/append-bidder-names.json
+++ b/exchange/exchangetest/append-bidder-names.json
@@ -166,9 +166,6 @@
     },
     "ext": {
       "debug": {
-        "httpcalls": {
-          "appnexus": null
-        },
         "resolvedrequest": {
           "id": "some-request-id",
           "imp": [
diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json
index 3dd04f66..e95b556b 100644
--- a/exchange/exchangetest/debuglog_disabled.json
+++ b/exchange/exchangetest/debuglog_disabled.json
@@ -175,9 +175,6 @@
     },
     "ext": {
       "debug": {
-        "httpcalls": {
-          "appnexus": null
-        },
         "resolvedrequest": {
           "id": "some-request-id",
           "imp": [
diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json
index a70013b4..851bda69 100644
--- a/exchange/exchangetest/debuglog_enabled.json
+++ b/exchange/exchangetest/debuglog_enabled.json
@@ -175,9 +175,6 @@
     },
     "ext": {
       "debug": {
-        "httpcalls": {
-          "appnexus": null
-        },
         "resolvedrequest": {
           "id": "some-request-id",
           "imp": [
diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json
index ec174f75..5061798b 100644
--- a/exchange/exchangetest/request-multi-bidders-debug-info.json
+++ b/exchange/exchangetest/request-multi-bidders-debug-info.json
@@ -65,6 +65,7 @@
           {
             "uri": "appnexusTest.com",
             "requestbody": "appnexusTestRequestBody",
+            "requestheaders": { "header_1" : ["value_11", "value_12"], "header_2" : ["value_21"] },
             "responsebody": "appnexusTestResponseBody",
             "status": 200
           }
@@ -94,6 +95,7 @@
           {
             "uri": "audienceNetworkTest.com",
             "requestbody": "audienceNetworkTestRequestBody",
+            "requestheaders": null,
             "responsebody": "audienceNetworkTestResponseBody",
             "status": 200
           }
@@ -149,6 +151,7 @@
             {
               "uri": "appnexusTest.com",
               "requestbody": "appnexusTestRequestBody",
+              "requestheaders": { "header_1" : ["value_11", "value_12"], "header_2" : ["value_21"] },
               "responsebody": "appnexusTestResponseBody",
               "status": 200
             }
@@ -157,6 +160,7 @@
             {
               "uri": "audienceNetworkTest.com",
               "requestbody": "audienceNetworkTestRequestBody",
+              "requestheaders": null,
               "responsebody": "audienceNetworkTestResponseBody",
               "status": 200
             }
diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go
index fcc8b09f..2cef4fea 100644
--- a/exchange/legacy_test.go
+++ b/exchange/legacy_test.go
@@ -61,7 +61,7 @@ func TestSiteVideo(t *testing.T) {
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 	if len(errs) > 0 {
 		t.Errorf("Unexpected error requesting bids: %v", errs)
 	}
@@ -95,7 +95,7 @@ func TestAppBanner(t *testing.T) {
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 	if len(errs) > 0 {
 		t.Errorf("Unexpected error requesting bids: %v", errs)
 	}
@@ -141,7 +141,7 @@ func TestBidTransforms(t *testing.T) {
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 	if len(errs) != 1 {
 		t.Fatalf("Bad error count. Expected 1, got %d", len(errs))
 	}
@@ -290,7 +290,7 @@ func TestErrorResponse(t *testing.T) {
 
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	_, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 	if len(errs) != 1 {
 		t.Fatalf("Bad error count. Expected 1, got %d", len(errs))
 	}
@@ -329,7 +329,7 @@ func TestWithTargeting(t *testing.T) {
 	}
 	exchangeBidder := adaptLegacyAdapter(&mockAdapter)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderAudienceNetwork, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{})
+	bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderAudienceNetwork, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
 	if len(errs) != 0 {
 		t.Fatalf("This should not produce errors. Got %v", errs)
 	}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 160d7465..2a77c4f7 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -115,7 +115,8 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		UserSyncs:  &emptyUsersync{},
 	}
 
-	bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, nil)
+	debugLog := DebugLog{}
+	bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog)
 
 	if err != nil {
 		t.Fatalf("Unexpected errors running auction: %v", err)
@@ -141,7 +142,7 @@ func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerU
 		adapterMap[bidder] = adaptBidder(&mockTargetingBidder{
 			mockServerURL: mockServerURL,
 			bids:          bids,
-		}, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus)
+		}, client, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	}
 	return adapterMap
 }
diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go
new file mode 100644
index 00000000..902bf14e
--- /dev/null
+++ b/gdpr/gdpr_test.go
@@ -0,0 +1,50 @@
+package gdpr
+
+import (
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewPermissions(t *testing.T) {
+	tests := []struct {
+		description  string
+		gdprEnabled  bool
+		hostVendorID int
+		wantType     Permissions
+	}{
+		{
+			gdprEnabled:  false,
+			hostVendorID: 32,
+			wantType:     &AlwaysAllow{},
+		},
+		{
+			gdprEnabled:  true,
+			hostVendorID: 0,
+			wantType:     &AllowHostCookies{},
+		},
+		{
+			gdprEnabled:  true,
+			hostVendorID: 32,
+			wantType:     &permissionsImpl{},
+		},
+	}
+
+	for _, tt := range tests {
+
+		config := config.GDPR{
+			Enabled:      tt.gdprEnabled,
+			HostVendorID: tt.hostVendorID,
+		}
+		vendorIDs := map[openrtb_ext.BidderName]uint16{}
+
+		perms := NewPermissions(context.Background(), config, vendorIDs, &http.Client{})
+
+		assert.IsType(t, tt.wantType, perms, tt.description)
+	}
+}
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index 62c6a5f9..77f3f294 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -28,63 +28,24 @@ func TestTCF1FetcherInitialLoad(t *testing.T) {
 
 	testCases := []test{
 		{
-			description: "Fetch - No Fallback - Vendor List 1",
+			description: "Fallback - Vendor List 1",
 			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  1,
-			},
-			expected: vendorList1Expected,
-		},
-		{
-			description: "Fetch - No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "Fetch - Fallback - Vendor List 1",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  1,
-			},
-			expected: vendorList1Expected,
-		},
-		{
-			description: "Fetch - Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "No Fetch - Fallback - Vendor List 1",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: true,
 				vendorListVersion:  1,
 			},
 			expected: vendorListFallbackExpected,
 		},
 		{
-			description: "No Fetch - Fallback - Vendor List 2",
+			description: "Fallback - Vendor List 2",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: true,
 				vendorListVersion:  2,
 			},
 			expected: vendorListFallbackExpected,
 		},
 		{
-			description: "No Fetch - No Fallback - Vendor List 1",
+			description: "No Fallback - Vendor List 1",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: false,
 				vendorListVersion:  1,
 			},
@@ -93,9 +54,8 @@ func TestTCF1FetcherInitialLoad(t *testing.T) {
 			},
 		},
 		{
-			description: "No Fetch - No Fallback - Vendor List 2",
+			description: "No Fallback - Vendor List 2",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: false,
 				vendorListVersion:  2,
 			},
@@ -125,72 +85,32 @@ func TestTCF2FetcherInitialLoad(t *testing.T) {
 
 	testCases := []test{
 		{
-			description: "Fetch - No Fallback - Vendor List 1",
+			description: "Fallback - Vendor List 1",
 			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  1,
-			},
-			expected: vendorList1Expected,
-		},
-		{
-			description: "Fetch - No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "Fetch - Fallback - Vendor List 1",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  1,
-			},
-			expected: vendorList1Expected,
-		},
-		{
-			description: "Fetch - Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "No Fetch - Fallback - Vendor List 1",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: true,
 				vendorListVersion:  1,
 			},
 			expected: vendorList1Expected,
 		},
 		{
-			description: "No Fetch - Fallback - Vendor List 2",
+			description: "Fallback - Vendor List 2",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: true,
 				vendorListVersion:  2,
 			},
 			expected: vendorList2Expected,
 		},
 		{
-			description: "No Fetch - No Fallback - Vendor List 1",
+			description: "No Fallback - Vendor List 1",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: false,
 				vendorListVersion:  1,
 			},
 			expected: vendorList1Expected,
 		},
 		{
-			description: "No Fetch - No Fallback - Vendor List 2",
+			description: "No Fallback - Vendor List 2",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: false,
 				vendorListVersion:  2,
 			},
@@ -203,65 +123,6 @@ func TestTCF2FetcherInitialLoad(t *testing.T) {
 	}
 }
 
-func TestTCF1FetcherDynamicLoadListExists(t *testing.T) {
-	// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
-	// All other vendor lists will be dynamically loaded.
-
-	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
-		vendorListLatestVersion: 1,
-		vendorLists: map[int]string{
-			1: tcf1VendorList1,
-			2: tcf1VendorList2,
-		},
-	})))
-	defer server.Close()
-
-	testCases := []test{
-		{
-			description: "Fetch - No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "Fetch - Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "No Fetch - Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorListFallbackExpected,
-		},
-		{
-			description: "No Fetch - No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
-		},
-	}
-
-	for _, test := range testCases {
-		runTest(t, test, tcf1SpecVersion, server)
-	}
-}
-
 func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
 	// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
 	// All other vendor lists will be dynamically loaded.
@@ -278,36 +139,16 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
 
 	testCases := []test{
 		{
-			description: "Fetch - No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "Fetch - Fallback - Vendor List 2",
+			description: "Fallback - Vendor List 2",
 			setup: testSetup{
-				enableTCF1Fetch:    true,
 				enableTCF1Fallback: true,
 				vendorListVersion:  2,
 			},
 			expected: vendorList2Expected,
 		},
 		{
-			description: "No Fetch - Fallback - Vendor List 2",
+			description: "No Fallback - Vendor List 2",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorList2Expected,
-		},
-		{
-			description: "No Fetch - No Fallback - Vendor List 2",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: false,
 				vendorListVersion:  2,
 			},
@@ -320,66 +161,6 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) {
 	}
 }
 
-func TestTCF1FetcherDynamicLoadListDoesntExist(t *testing.T) {
-	// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
-	// All other vendor list load attempts will be done dynamically.
-
-	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
-		vendorListLatestVersion: 1,
-		vendorLists: map[int]string{
-			1: tcf1VendorList1,
-		},
-	})))
-	defer server.Close()
-
-	testCases := []test{
-		{
-			description: "Fetch - No Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
-		},
-		{
-			description: "Fetch - Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorListFallbackExpected,
-		},
-		{
-			description: "No Fetch - Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: vendorListFallbackExpected,
-		},
-		{
-			description: "No Fetch - No Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fetch:    false,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
-		},
-	}
-
-	for _, test := range testCases {
-		runTest(t, test, 1, server)
-	}
-}
-
 func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
 	// Loads the first vendor list during initialization by setting the latest vendor list version to 1.
 	// All other vendor list load attempts will be done dynamically.
@@ -395,31 +176,8 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
 
 	testCases := []test{
 		{
-			description: "Fetch - No Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: false,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
-		},
-		{
-			description: "Fetch - Fallback - Vendor Doesn't Exist",
-			setup: testSetup{
-				enableTCF1Fetch:    true,
-				enableTCF1Fallback: true,
-				vendorListVersion:  2,
-			},
-			expected: testExpected{
-				errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes",
-			},
-		},
-		{
-			description: "No Fetch - Fallback - Vendor Doesn't Exist",
+			description: "Fallback - Vendor Doesn't Exist",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: true,
 				vendorListVersion:  2,
 			},
@@ -428,9 +186,8 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
 			},
 		},
 		{
-			description: "No Fetch - No Fallback - Vendor Doesn't Exist",
+			description: "No Fallback - Vendor Doesn't Exist",
 			setup: testSetup{
-				enableTCF1Fetch:    false,
 				enableTCF1Fallback: false,
 				vendorListVersion:  2,
 			},
@@ -445,38 +202,6 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) {
 	}
 }
 
-func TestTCF1FetcherThrottling(t *testing.T) {
-	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
-		vendorListLatestVersion: 1,
-		vendorLists: map[int]string{
-			1: tcf1MarshalVendorList(tcf1VendorList{
-				VendorListVersion: 1,
-				Vendors:           []tcf1Vendor{{ID: 12, Purposes: []int{1}}},
-			}),
-			2: tcf1MarshalVendorList(tcf1VendorList{
-				VendorListVersion: 2,
-				Vendors:           []tcf1Vendor{{ID: 12, Purposes: []int{1, 2}}},
-			}),
-			3: tcf1MarshalVendorList(tcf1VendorList{
-				VendorListVersion: 3,
-				Vendors:           []tcf1Vendor{{ID: 12, Purposes: []int{1, 2, 3}}},
-			}),
-		},
-	})))
-	defer server.Close()
-
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion)
-
-	// Dynamically Load List 2 Successfully
-	_, errList1 := fetcher(context.Background(), 2)
-	assert.NoError(t, errList1)
-
-	// Fail To Load List 3 Due To Rate Limiting
-	// - The request is rate limited after dynamically list 2.
-	_, errList2 := fetcher(context.Background(), 3)
-	assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes")
-}
-
 func TestTCF2FetcherThrottling(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
 		vendorListLatestVersion: 1,
@@ -509,22 +234,6 @@ func TestTCF2FetcherThrottling(t *testing.T) {
 	assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes")
 }
 
-func TestTCF1MalformedVendorlist(t *testing.T) {
-	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
-		vendorListLatestVersion: 1,
-		vendorLists: map[int]string{
-			1: "malformed",
-		},
-	})))
-	defer server.Close()
-
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion)
-	_, err := fetcher(context.Background(), 1)
-
-	// Fetching should fail since vendor list could not be unmarshalled.
-	assert.Error(t, err)
-}
-
 func TestTCF2MalformedVendorlist(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{
 		vendorListLatestVersion: 1,
@@ -541,18 +250,6 @@ func TestTCF2MalformedVendorlist(t *testing.T) {
 	assert.Error(t, err)
 }
 
-func TestTCF1ServerUrlInvalid(t *testing.T) {
-	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
-	server.Close()
-
-	invalidURLGenerator := func(uint16, uint8) string { return " http://invalid-url-has-leading-whitespace" }
-
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator, tcf1SpecVersion)
-	_, err := fetcher(context.Background(), 1)
-
-	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
-}
-
 func TestTCF2ServerUrlInvalid(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 	server.Close()
@@ -565,16 +262,6 @@ func TestTCF2ServerUrlInvalid(t *testing.T) {
 	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
 }
 
-func TestTCF1ServerUnavailable(t *testing.T) {
-	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
-	server.Close()
-
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf1SpecVersion)
-	_, err := fetcher(context.Background(), 1)
-
-	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
-}
-
 func TestTCF2ServerUnavailable(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 	server.Close()
@@ -740,7 +427,6 @@ type test struct {
 }
 
 type testSetup struct {
-	enableTCF1Fetch    bool
 	enableTCF1Fallback bool
 	vendorListVersion  uint16
 }
@@ -754,7 +440,6 @@ type testExpected struct {
 
 func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Server) {
 	config := testConfig()
-	config.TCF1.FetchGVL = test.setup.enableTCF1Fetch
 	if test.setup.enableTCF1Fallback {
 		config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
 	}
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index bca8623d..73f0e886 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -101,6 +101,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,