Repository: prebid/prebid-server
Pull request #2071 (2021-11-03 23:36:39)

Currency conversion on adapter JSON tests
Pull request #1901 granted adapters the ability to convert currency inside their `MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo)` implementation. This pull request makes possible for adapters to include their own currency conversion rates in their JSON tests in order to better assert #1901's features

Also commited in this PR are JSON tests for the impactify adapter. Impactify's implementation of `MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo)` makes use of currency conversion and the additions to `adapters/impactify/impactifytest/exemplary/sample_banner.json` assert the correct conversion fo the impression bid floor, and on the other hand `adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json` asserts a currency conversion error scenario.

@thomasdseao we'd like to hear your take on the tests added to your adapter. Please let us know if you approve.

mansinahar: @thomasdseao This PR makes some changes to the Impactify adpater's JSON tests to be able to test the currency conversion functionality being used in the `MakeRequests` implementation of Impactify. Can you please take a look when you have a chance and verify if the changes to those tests look good to you? Thanks!

Patch:
diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go
new file mode 100644
index 00000000..5587cac7
--- /dev/null
+++ b/adapters/openrtb_util.go
@@ -0,0 +1,61 @@
+package adapters
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func ExtractAdapterReqBidderParams(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) {
+	if bidRequest == nil {
+		return nil, errors.New("error bidRequest should not be nil")
+	}
+
+	reqExt := &openrtb_ext.ExtRequest{}
+	if len(bidRequest.Ext) > 0 {
+		err := json.Unmarshal(bidRequest.Ext, &reqExt)
+		if err != nil {
+			return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error())
+		}
+	}
+
+	if reqExt.Prebid.BidderParams == nil {
+		return nil, nil
+	}
+
+	var bidderParams map[string]json.RawMessage
+	err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams)
+	if err != nil {
+		return nil, err
+	}
+
+	return bidderParams, nil
+}
+
+func ExtractReqExtBidderParams(bidReq *openrtb2.BidRequest) (map[string]map[string]json.RawMessage, error) {
+	if bidReq == nil {
+		return nil, errors.New("error bidRequest should not be nil")
+	}
+
+	reqExt := &openrtb_ext.ExtRequest{}
+	if len(bidReq.Ext) > 0 {
+		err := json.Unmarshal(bidReq.Ext, &reqExt)
+		if err != nil {
+			return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error())
+		}
+	}
+
+	if reqExt.Prebid.BidderParams == nil {
+		return nil, nil
+	}
+
+	var bidderParams map[string]map[string]json.RawMessage
+	err := json.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams)
+	if err != nil {
+		return nil, err
+	}
+
+	return bidderParams, nil
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 19024f4a..799d3364 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -26,6 +26,11 @@ type pubmaticBidExt struct {
 	VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"`
 }
 
+type pubmaticWrapperExt struct {
+	ProfileID int `json:"profile,omitempty"`
+	VersionID int `json:"version,omitempty"`
+}
+
 type pubmaticBidExtVideo struct {
 	Duration *int `json:"duration,omitempty"`
 }
@@ -56,16 +61,51 @@ const (
 func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	errs := make([]error, 0, len(request.Imp))
 
-	wrapExt := ""
 	pubID := ""
+	var wrapperExt *pubmaticWrapperExt
+	extractWrapperExtFromImp := true
+	extractPubIDFromImp := true
+
+	wrapperExt, err := extractPubmaticWrapperExtFromRequest(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+	if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 {
+		extractWrapperExtFromImp = false
+	}
 
 	for i := 0; i < len(request.Imp); i++ {
-		err := parseImpressionObject(&request.Imp[i], &wrapExt, &pubID)
+		wrapperExtFromImp, pubIDFromImp, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp)
+
 		// If the parsing is failed, remove imp and add the error.
 		if err != nil {
 			errs = append(errs, err)
 			request.Imp = append(request.Imp[:i], request.Imp[i+1:]...)
 			i--
+			continue
+		}
+
+		if extractWrapperExtFromImp {
+			if wrapperExtFromImp != nil {
+				if wrapperExt == nil {
+					wrapperExt = &pubmaticWrapperExt{}
+				}
+				if wrapperExt.ProfileID == 0 {
+					wrapperExt.ProfileID = wrapperExtFromImp.ProfileID
+				}
+				if wrapperExt.VersionID == 0 {
+					wrapperExt.VersionID = wrapperExtFromImp.VersionID
+				}
+
+				if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 {
+					extractWrapperExtFromImp = false
+				}
+			}
+		}
+
+		if extractPubIDFromImp && pubIDFromImp != "" {
+			pubID = pubIDFromImp
+			extractPubIDFromImp = false
 		}
 	}
 
@@ -74,9 +114,14 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad
 		return nil, errs
 	}
 
-	if wrapExt != "" {
-		rawExt := fmt.Sprintf("{\"wrapper\": %s}", wrapExt)
-		request.Ext = json.RawMessage(rawExt)
+	if wrapperExt != nil {
+		reqExt := make(map[string]interface{})
+		reqExt["wrapper"] = wrapperExt
+		rawExt, err := json.Marshal(reqExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+		request.Ext = rawExt
 	}
 
 	if request.Site != nil {
@@ -179,10 +224,13 @@ func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.B
 }
 
 // parseImpressionObject parse the imp to get it ready to send to pubmatic
-func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) error {
+func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, error) {
+	var wrapExt *pubmaticWrapperExt
+	var pubID string
+
 	// PubMatic supports banner and video impressions.
 	if imp.Banner == nil && imp.Video == nil {
-		return fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID)
+		return wrapExt, pubID, fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID)
 	}
 
 	if imp.Audio != nil {
@@ -191,36 +239,34 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er
 
 	var bidderExt ExtImpBidderPubmatic
 	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
-		return err
+		return wrapExt, pubID, err
 	}
 
 	var pubmaticExt openrtb_ext.ExtImpPubmatic
 	if err := json.Unmarshal(bidderExt.Bidder, &pubmaticExt); err != nil {
-		return err
+		return wrapExt, pubID, err
 	}
 
-	if *pubID == "" {
-		*pubID = strings.TrimSpace(pubmaticExt.PublisherId)
+	if extractPubIDFromImp {
+		pubID = strings.TrimSpace(pubmaticExt.PublisherId)
 	}
 
 	// Parse Wrapper Extension only once per request
-	if *wrapExt == "" && len(pubmaticExt.WrapExt) != 0 {
-		var wrapExtMap map[string]int
-		err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExtMap)
+	if extractWrapperExtFromImp && len(pubmaticExt.WrapExt) != 0 {
+		err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExt)
 		if err != nil {
-			return fmt.Errorf("Error in Wrapper Parameters = %v  for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt))
+			return wrapExt, pubID, fmt.Errorf("Error in Wrapper Parameters = %v  for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt))
 		}
-		*wrapExt = string(pubmaticExt.WrapExt)
 	}
 
 	if err := validateAdSlot(strings.TrimSpace(pubmaticExt.AdSlot), imp); err != nil {
-		return err
+		return wrapExt, pubID, err
 	}
 
 	if imp.Banner != nil {
 		bannerCopy, err := assignBannerSize(imp.Banner)
 		if err != nil {
-			return err
+			return wrapExt, pubID, err
 		}
 		imp.Banner = bannerCopy
 	}
@@ -253,7 +299,26 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *string, pubID *string) er
 		}
 	}
 
-	return nil
+	return wrapExt, pubID, nil
+}
+
+// extractPubmaticWrapperExtFromRequest parse the imp to get it ready to send to pubmatic
+func extractPubmaticWrapperExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, error) {
+	var wrpExt pubmaticWrapperExt
+	reqExtBidderParams, err := adapters.ExtractAdapterReqBidderParams(request)
+	if err != nil {
+		return nil, err
+	}
+
+	//get request ext bidder params
+	if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 {
+		err = json.Unmarshal(wrapperObj, &wrpExt)
+		if err != nil {
+			return nil, err
+		}
+		return &wrpExt, nil
+	}
+	return nil, nil
 }
 
 func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) {
diff --git a/adapters/videobyte/videobyte.go b/adapters/videobyte/videobyte.go
new file mode 100644
index 00000000..a4c0df61
--- /dev/null
+++ b/adapters/videobyte/videobyte.go
@@ -0,0 +1,158 @@
+package videobyte
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"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/mxmCherry/openrtb/v15/openrtb2"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	impressions := request.Imp
+	adapterRequests := make([]*adapters.RequestData, 0, len(impressions))
+	var errs []error
+
+	for _, impression := range impressions {
+		impExt, err := parseExt(&impression)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		request.Imp = []openrtb2.Imp{impression}
+		body, err := json.Marshal(request)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		adapterRequests = append(adapterRequests, &adapters.RequestData{
+			Method:  http.MethodPost,
+			Uri:     a.endpoint + "?" + getParams(impExt).Encode(),
+			Body:    body,
+			Headers: getHeaders(request),
+		})
+	}
+
+	request.Imp = impressions
+	return adapterRequests, errs
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Bad user input: HTTP status %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	var ortbResponse openrtb2.BidResponse
+	err := json.Unmarshal(response.Body, &ortbResponse)
+	if err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Bad Server Response",
+		}}
+	}
+
+	impIdToImp := make(map[string]*openrtb2.Imp)
+	for i := range internalRequest.Imp {
+		imp := internalRequest.Imp[i]
+		impIdToImp[imp.ID] = &imp
+	}
+
+	bidderResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+	for _, seatBid := range ortbResponse.SeatBid {
+		for i := range seatBid.Bid {
+			bid := seatBid.Bid[i]
+			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+				Bid:     &bid,
+				BidType: getMediaTypeForImp(impIdToImp[bid.ImpID]),
+			})
+		}
+	}
+
+	return bidderResponse, nil
+}
+
+func getMediaTypeForImp(imp *openrtb2.Imp) openrtb_ext.BidType {
+	if imp != nil && imp.Banner != nil {
+		return openrtb_ext.BidTypeBanner
+	}
+	return openrtb_ext.BidTypeVideo
+}
+
+func getParams(impExt *openrtb_ext.ExtImpVideoByte) url.Values {
+	params := url.Values{}
+	params.Add("source", "pbs")
+	params.Add("pid", impExt.PublisherId)
+	if impExt.PlacementId != "" {
+		params.Add("placementId", impExt.PlacementId)
+	}
+	if impExt.NetworkId != "" {
+		params.Add("nid", impExt.NetworkId)
+	}
+	return params
+}
+
+func getHeaders(request *openrtb2.BidRequest) http.Header {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	if request.Site != nil {
+		if request.Site.Domain != "" {
+			headers.Add("Origin", request.Site.Domain)
+		}
+		if request.Site.Ref != "" {
+			headers.Set("Referer", request.Site.Ref)
+		}
+	}
+	return headers
+}
+
+func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpVideoByte, error) {
+	var bidderExt adapters.ExtImpBidder
+
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err),
+		}
+	}
+
+	impExt := openrtb_ext.ExtImpVideoByte{}
+	err := json.Unmarshal(bidderExt.Bidder, &impExt)
+	if err != nil {
+		return nil, &errortypes.BadInput{
+			Message: fmt.Sprintf("Ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err),
+		}
+	}
+
+	return &impExt, nil
+}
diff --git a/config/config.go b/config/config.go
index 83585146..e64562e6 100644
--- a/config/config.go
+++ b/config/config.go
@@ -895,6 +895,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.unruly.endpoint", "https://targeting.unrulymedia.com/unruly_prebid_server")
 	v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint")
 	v.SetDefault("adapters.verizonmedia.disabled", true)
+	v.SetDefault("adapters.videobyte.endpoint", "https://x.videobyte.com/ortbhb")
 	v.SetDefault("adapters.viewdeos.endpoint", "http://ghb.sync.viewdeos.com/pbs/ortb")
 	v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.0")
 	v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804")
diff --git a/currency/validation.go b/currency/validation.go
new file mode 100644
index 00000000..7a0e2aa0
--- /dev/null
+++ b/currency/validation.go
@@ -0,0 +1,34 @@
+package currency
+
+import (
+	"fmt"
+
+	"golang.org/x/text/currency"
+
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// ValidateCustomRates throws a bad input error if any of the 3-digit currency codes found in
+// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual
+// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty.
+func ValidateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error {
+	if bidReqCurrencyRates == nil {
+		return nil
+	}
+
+	for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates {
+		// Check if fromCurrency is a valid 3-letter currency code
+		if _, err := currency.ParseISO(fromCurrency); err != nil {
+			return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)}
+		}
+
+		// Check if currencies mapped to fromCurrency are valid 3-letter currency codes
+		for toCurrency := range rates {
+			if _, err := currency.ParseISO(toCurrency); err != nil {
+				return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)}
+			}
+		}
+	}
+	return nil
+}
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index ec9e4152..2b05e9aa 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -23,8 +23,10 @@ import (
 	nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request"
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	accountService "github.com/prebid/prebid-server/account"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/metrics"
@@ -39,7 +41,6 @@ import (
 	"github.com/prebid/prebid-server/util/iputil"
 	"github.com/prebid/prebid-server/util/uuidutil"
 	"golang.org/x/net/publicsuffix"
-	"golang.org/x/text/currency"
 )
 
 const storedRequestTimeoutMillis = 50
@@ -290,6 +291,11 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_
 		return
 	}
 
+	if err := mergeBidderParams(req); err != nil {
+		errs = []error{err}
+		return
+	}
+
 	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
 	deps.setFieldsImplicitly(httpRequest, req.BidRequest)
 
@@ -323,6 +329,117 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio
 	return defaultTimeout
 }
 
+// mergeBidderParams merges bidder parameters passed at req.ext level with imp[].ext level.
+// Preference is given to parameters at imp[].ext level over req.ext level.
+// Parameters at req.ext level are propagated to adapters as is without any validation.
+func mergeBidderParams(req *openrtb_ext.RequestWrapper) error {
+	reqBidderParams, err := adapters.ExtractReqExtBidderParams(req.BidRequest)
+	if err != nil {
+		return err
+	}
+
+	impCpy := make([]openrtb2.Imp, 0, len(req.BidRequest.Imp))
+	for _, imp := range req.BidRequest.Imp {
+		updatedImp := imp
+
+		if len(imp.Ext) == 0 {
+			impCpy = append(impCpy, updatedImp)
+			continue
+		}
+
+		var impExt map[string]map[string]json.RawMessage
+		err := json.Unmarshal(imp.Ext, &impExt)
+		if err != nil {
+			return err
+		}
+
+		//merges bidder parameters passed at req.ext level with imp[].ext level.
+		err = addMissingReqExtParamsInImpExt(impExt, reqBidderParams)
+		if err != nil {
+			return err
+		}
+
+		//merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level.
+		err = addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams)
+		if err != nil {
+			return err
+		}
+
+		iExt, err := json.Marshal(impExt)
+		if err != nil {
+			return fmt.Errorf("error marshalling imp[].ext : %s", err.Error())
+		}
+		updatedImp.Ext = iExt
+		impCpy = append(impCpy, updatedImp)
+	}
+
+	req.BidRequest.Imp = impCpy
+	return nil
+}
+
+// addMissingReqExtParamsInImpExtPrebid merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level.
+func addMissingReqExtParamsInImpExtPrebid(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error {
+	var bidderParams map[string]json.RawMessage
+	if impExtBidder["prebid"] != nil && impExtBidder["prebid"]["bidder"] != nil {
+		err := json.Unmarshal(impExtBidder["prebid"]["bidder"], &bidderParams)
+		if err != nil {
+			return err
+		}
+	}
+
+	if len(bidderParams) != 0 {
+		for bidder, bidderExt := range bidderParams {
+			if !isBidderToValidate(bidder) {
+				continue
+			}
+
+			var params map[string]json.RawMessage
+			err := json.Unmarshal(bidderExt, &params)
+
+			for key, value := range reqExtParams[bidder] {
+				if _, present := params[key]; !present {
+					params[key] = value
+				}
+			}
+
+			paramsJson, err := json.Marshal(params)
+			if err != nil {
+				return err
+			}
+			bidderParams[bidder] = paramsJson
+		}
+
+		bidderParamsJson, err := json.Marshal(bidderParams)
+		if err != nil {
+			return err
+		}
+		impExtBidder["prebid"]["bidder"] = bidderParamsJson
+	}
+
+	return nil
+}
+
+// addMissingReqExtParamsInImpExt merges bidder parameters passed at req.ext level with imp[].ext level.
+func addMissingReqExtParamsInImpExt(impExtBidder map[string]map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) error {
+	for bidder, bidderExt := range impExtBidder {
+		if !isBidderToValidate(bidder) {
+			continue
+		}
+
+		wasModified := false
+		for key, value := range reqExtParams[bidder] {
+			if _, present := bidderExt[key]; !present {
+				bidderExt[key] = value
+				wasModified = true
+			}
+		}
+		if wasModified {
+			impExtBidder[bidder] = bidderExt
+		}
+	}
+	return nil
+}
+
 func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error {
 	errL := []error{}
 	if req.ID == "" {
@@ -377,7 +494,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []err
 			return []error{err}
 		}
 
-		if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil {
+		if err := currency.ValidateCustomRates(reqPrebid.CurrencyConversions); err != nil {
 			return []error{err}
 		}
 	}
@@ -475,30 +592,6 @@ func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error {
 	return err
 }
 
-// validateCustomRates throws a bad input error if any of the 3-digit currency codes found in
-// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual
-// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty.
-func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error {
-	if bidReqCurrencyRates == nil {
-		return nil
-	}
-
-	for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates {
-		// Check if fromCurrency is a valid 3-letter currency code
-		if _, err := currency.ParseISO(fromCurrency); err != nil {
-			return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)}
-		}
-
-		// Check if currencies mapped to fromCurrency are valid 3-letter currency codes
-		for toCurrency := range rates {
-			if _, err := currency.ParseISO(toCurrency); err != nil {
-				return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)}
-			}
-		}
-	}
-	return nil
-}
-
 func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error {
 	if prebid == nil {
 		return nil
@@ -1077,6 +1170,8 @@ func isBidderToValidate(bidder string) bool {
 		return false
 	case openrtb_ext.BidderReservedSKAdN:
 		return false
+	case openrtb_ext.BidderReservedBidder:
+		return false
 	default:
 		return true
 	}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
index 76dc1c2b..5baaa09d 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/cache-nothing.json
@@ -27,5 +27,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n"
+  "expectedErrorMessage": "Invalid request: error decoding Request.ext : request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
index d9ed59c8..b1e9b784 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
@@ -20,5 +20,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n"
+  "expectedErrorMessage": "Invalid request: json: cannot unmarshal string into Go value of type map[string]json.RawMessage"
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json
new file mode 100644
index 00000000..274c2c70
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-backward-compatible-merge.json
@@ -0,0 +1,101 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 111111
+          },
+          "pubmatic": {
+            "wrapper": {
+              "profile": 1234
+            }
+          },
+          "prebid": {
+            "bidder": {}
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "appnexus": {
+            "placementId": 222222
+          },
+          "pubmatic": {
+            "publisherId": "5678",
+            "wrapper": {
+              "profile": 5678,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "appnexus": {
+          "placementId": 222222
+        },
+        "pubmatic": {
+          "publisherId": "5678",
+          "wrapper": {
+            "profile": 5678,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "appnexus": {
+      "placementId": 111111
+    },
+    "pubmatic": {
+      "publisherId": "5678",
+      "wrapper": {
+        "profile": 1234
+      }
+    },
+    "prebid": {
+      "bidder": {}
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json
new file mode 100644
index 00000000..982f86cb
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge-reserved-keyword.json
@@ -0,0 +1,116 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "bidder": {
+                "param1": 111111
+              },
+              "appnexus": {
+                "placementId": 111111
+              },
+              "pubmatic": {
+                "publisherId": "1234",
+                "wrapper": {
+                  "profile": 1234
+                }
+              }
+            }
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "bidder": {
+            "param2": 111111
+          },
+          "appnexus": {
+            "placementId": 222222
+          },
+          "pubmatic": {
+            "publisherId": "5678",
+            "wrapper": {
+              "profile": 5678,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "bidder": {
+          "param2": 111111
+        },
+        "appnexus": {
+          "placementId": 222222
+        },
+        "pubmatic": {
+          "publisherId": "5678",
+          "wrapper": {
+            "profile": 5678,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "prebid": {
+      "bidder": {
+        "bidder": {
+          "param1": 111111
+        },
+        "appnexus": {
+          "placementId": 111111
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json
new file mode 100644
index 00000000..22026f56
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params-merge.json
@@ -0,0 +1,104 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 111111
+              },
+              "pubmatic": {
+                "publisherId": "1234",
+                "wrapper": {
+                  "profile": 1234
+                }
+              }
+            }
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "appnexus": {
+            "placementId": 222222
+          },
+          "pubmatic": {
+            "publisherId": "5678",
+            "wrapper": {
+              "profile": 5678,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "appnexus": {
+          "placementId": 222222
+        },
+        "pubmatic": {
+          "publisherId": "5678",
+          "wrapper": {
+            "profile": 5678,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "prebid": {
+      "bidder": {
+        "appnexus": {
+          "placementId": 111111
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json
new file mode 100644
index 00000000..9d4bcd0d
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/req-ext-bidder-params.json
@@ -0,0 +1,98 @@
+{
+  "description": "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder",
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "test.somepage.com"
+    },
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "pmp": {
+          "deals": [
+            {
+              "id": "some-deal-id"
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {},
+              "pubmatic": {}
+            }
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "targeting": {
+          "pricegranularity": "low"
+        },
+        "cache": {
+          "bids": {}
+        },
+        "bidderparams": {
+          "appnexus": {
+            "placementId": 12883451
+          },
+          "pubmatic": {
+            "publisherId": "1234",
+            "wrapper": {
+              "profile": 1234,
+              "version": 2
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedReqExt": {
+    "prebid": {
+      "targeting": {
+        "pricegranularity": "low"
+      },
+      "cache": {
+        "bids": {}
+      },
+      "bidderparams": {
+        "appnexus": {
+          "placementId": 12883451
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedImpExt": {
+    "prebid": {
+      "bidder": {
+        "appnexus": {
+          "placementId": 12883451
+        },
+        "pubmatic": {
+          "publisherId": "1234",
+          "wrapper": {
+            "profile": 1234,
+            "version": 2
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 33d662c2..fa1882f3 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -121,6 +121,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/unicorn"
 	"github.com/prebid/prebid-server/adapters/unruly"
 	"github.com/prebid/prebid-server/adapters/valueimpression"
+	"github.com/prebid/prebid-server/adapters/videobyte"
 	"github.com/prebid/prebid-server/adapters/visx"
 	"github.com/prebid/prebid-server/adapters/vrtcal"
 	"github.com/prebid/prebid-server/adapters/yahoossp"
@@ -261,6 +262,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderUnruly:            unruly.Builder,
 		openrtb_ext.BidderValueImpression:   valueimpression.Builder,
 		openrtb_ext.BidderVerizonMedia:      yahoossp.Builder,
+		openrtb_ext.BidderVideoByte:         videobyte.Builder,
 		openrtb_ext.BidderViewdeos:          adtelligent.Builder,
 		openrtb_ext.BidderVisx:              visx.Builder,
 		openrtb_ext.BidderVrtcal:            vrtcal.Builder,
diff --git a/exchange/utils.go b/exchange/utils.go
index 5a68ba45..23ffe5e5 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -11,6 +11,7 @@ import (
 	"github.com/prebid/go-gdpr/vendorconsent"
 
 	"github.com/buger/jsonparser"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/firstpartydata"
 	"github.com/prebid/prebid-server/gdpr"
@@ -222,6 +223,11 @@ func getAuctionBidderRequests(req AuctionRequest,
 		return nil, []error{err}
 	}
 
+	bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParams(req.BidRequest)
+	if err != nil {
+		return nil, []error{err}
+	}
+
 	var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain
 
 	// Quick extra wrapper until RequestWrapper makes its way into CleanRequests
@@ -232,21 +238,33 @@ func getAuctionBidderRequests(req AuctionRequest,
 		}
 	}
 
-	reqExt, err := getExtJson(req.BidRequest, requestExt)
-	if err != nil {
-		return nil, []error{err}
-	}
-
 	var errs []error
 	for bidder, imps := range impsByBidder {
 		coreBidder := resolveBidder(bidder, aliases)
 
 		reqCopy := *req.BidRequest
 		reqCopy.Imp = imps
-		reqCopy.Ext = reqExt
 
 		prepareSource(&reqCopy, bidder, sChainsByBidder)
 
+		if len(bidderParamsInReqExt) != 0 {
+
+			// Update bidder-params(requestExt.Prebid.BidderParams) for the bidder to only contain bidder-params for
+			// this bidder only and remove bidder-params for all other bidders from requestExt.Prebid.BidderParams
+			params, err := getBidderParamsForBidder(bidderParamsInReqExt, bidder)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			requestExt.Prebid.BidderParams = params
+		}
+
+		reqExt, err := getExtJson(req.BidRequest, requestExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+		reqCopy.Ext = reqExt
+
 		if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil {
 			errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err))
 			continue
@@ -278,6 +296,18 @@ func getAuctionBidderRequests(req AuctionRequest,
 	return bidderRequests, errs
 }
 
+func getBidderParamsForBidder(bidderParamsInReqExt map[string]map[string]json.RawMessage, bidder string) (json.RawMessage, error) {
+	var params json.RawMessage
+	if bidderParams, ok := bidderParamsInReqExt[bidder]; ok {
+		var err error
+		params, err = json.Marshal(bidderParams)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return params, nil
+}
+
 func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) {
 	if len(req.Ext) == 0 || unpackedExt == nil {
 		return json.RawMessage(``), nil
@@ -309,6 +339,9 @@ func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[
 	// set source
 	if req.Source == nil {
 		req.Source = &openrtb2.Source{}
+	} else {
+		sourceCopy := *req.Source
+		req.Source = &sourceCopy
 	}
 	schain := openrtb_ext.ExtRequestPrebidSChain{
 		SChain: *selectedSChain,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index d5c424c5..3c3f6764 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -34,6 +34,7 @@ const (
 	BidderReservedGeneral BidderName = "general" // Reserved for non-bidder specific messages when using a map keyed on the bidder name.
 	BidderReservedPrebid  BidderName = "prebid"  // Reserved for Prebid Server configuration.
 	BidderReservedSKAdN   BidderName = "skadn"   // Reserved for Apple's SKAdNetwork OpenRTB extension.
+	BidderReservedBidder  BidderName = "bidder"  // Reserved for passing bidder parameters.
 )
 
 // IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name.
@@ -62,6 +63,10 @@ func IsBidderNameReserved(name string) bool {
 		return true
 	}
 
+	if strings.EqualFold(name, string(BidderReservedBidder)) {
+		return true
+	}
+
 	return false
 }
 
@@ -195,6 +200,7 @@ const (
 	BidderUnruly            BidderName = "unruly"
 	BidderValueImpression   BidderName = "valueimpression"
 	BidderVerizonMedia      BidderName = "verizonmedia"
+	BidderVideoByte         BidderName = "videobyte"
 	BidderVisx              BidderName = "visx"
 	BidderViewdeos          BidderName = "viewdeos"
 	BidderVrtcal            BidderName = "vrtcal"
@@ -334,6 +340,7 @@ func CoreBidderNames() []BidderName {
 		BidderUnruly,
 		BidderValueImpression,
 		BidderVerizonMedia,
+		BidderVideoByte,
 		BidderViewdeos,
 		BidderVisx,
 		BidderVrtcal,
diff --git a/openrtb_ext/imp_videobyte.go b/openrtb_ext/imp_videobyte.go
new file mode 100644
index 00000000..9101e9f2
--- /dev/null
+++ b/openrtb_ext/imp_videobyte.go
@@ -0,0 +1,8 @@
+package openrtb_ext
+
+// ExtImpVideoByte defines the contract for bidrequest.imp[i].ext.videobyte
+type ExtImpVideoByte struct {
+	PublisherId string `json:"pubId"`
+	PlacementId string `json:"placementId"`
+	NetworkId   string `json:"nid"`
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index d399715f..a65e8d3c 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -37,6 +37,7 @@ type ExtRequestPrebid struct {
 	StoredRequest        *ExtStoredRequest         `json:"storedrequest,omitempty"`
 	SupportDeals         bool                      `json:"supportdeals,omitempty"`
 	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
+	BidderParams         json.RawMessage           `json:"bidderparams,omitempty"`
 
 	// NoSale specifies bidders with whom the publisher has a legal relationship where the
 	// passing of personally identifiable information doesn't constitute a sale per CCPA law.
diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml
index a8769ddd..0853cf10 100644
--- a/static/bidder-info/adyoulike.yaml
+++ b/static/bidder-info/adyoulike.yaml
@@ -9,6 +9,10 @@ capabilities:
       - video
       - native
 userSync:
+  default: iframe
+  iframe:
+    url: "http://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
+    userMacro: "[BUYER_USERID]"
   redirect:
     url: "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url={{.RedirectURL}}"
     userMacro: "[BUYER_USERID]"
diff --git a/static/bidder-info/videobyte.yaml b/static/bidder-info/videobyte.yaml
new file mode 100644
index 00000000..fc0e767e
--- /dev/null
+++ b/static/bidder-info/videobyte.yaml
@@ -0,0 +1,16 @@
+maintainer:
+  email: "prebid@videobyte.com"
+gvlVendorID: 1011
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
+userSync:
+  redirect:
+    url: "https://x.videobyte.com/usync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
+    userMacro: "$UID"
diff --git a/static/bidder-params/videobyte.json b/static/bidder-params/videobyte.json
new file mode 100644
index 00000000..a9388e26
--- /dev/null
+++ b/static/bidder-params/videobyte.json
@@ -0,0 +1,22 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "VideoByte Adapter Params",
+  "description": "A schema which validates params accepted by the VideoByte adapter",
+
+  "type": "object",
+  "properties": {
+    "pubId": {
+      "type": "string",
+      "description": "Publisher ID"
+    },
+    "placementId": {
+      "type": "string",
+      "description": "Placement ID"
+    },
+    "nid": {
+      "type": "string",
+      "description": "Network ID"
+    }
+  },
+  "required": ["pubId"]
+}


Test patch:
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index 1ec2fc08..453dccf7 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -10,6 +10,8 @@ import (
 	"github.com/mitchellh/copystructure"
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
 	"github.com/yudai/gojsondiff"
 	"github.com/yudai/gojsondiff/formatter"
@@ -103,7 +105,40 @@ func loadFile(filename string) (*testSpec, error) {
 //
 // More assertions will almost certainly be added in the future, as bugs come up.
 func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, isAmpTest, isVideoTest bool) {
-	reqInfo := adapters.ExtraRequestInfo{}
+	reqInfo := getTestExtraRequestInfo(t, filename, spec, isAmpTest, isVideoTest)
+	requests := testMakeRequestsImpl(t, filename, spec, bidder, reqInfo)
+
+	testMakeBidsImpl(t, filename, spec, bidder, requests)
+}
+
+// getTestExtraRequestInfo builds the ExtraRequestInfo object that will be passed to testMakeRequestsImpl
+func getTestExtraRequestInfo(t *testing.T, filename string, spec *testSpec, isAmpTest, isVideoTest bool) *adapters.ExtraRequestInfo {
+	t.Helper()
+
+	var reqInfo adapters.ExtraRequestInfo
+
+	// If test request.ext defines its own currency rates, add currency conversion to reqInfo
+	reqWrapper := &openrtb_ext.RequestWrapper{}
+	reqWrapper.BidRequest = &spec.BidRequest
+
+	reqExt, err := reqWrapper.GetRequestExt()
+	assert.NoError(t, err, "Could not unmarshall test request ext. %s", filename)
+
+	reqPrebid := reqExt.GetPrebid()
+	if reqPrebid != nil && reqPrebid.CurrencyConversions != nil && len(reqPrebid.CurrencyConversions.ConversionRates) > 0 {
+		err = currency.ValidateCustomRates(reqPrebid.CurrencyConversions)
+		assert.NoError(t, err, "Error validating currency rates in the test request: %s", filename)
+
+		// Get currency rates conversions from the test request.ext
+		conversions := currency.NewRates(reqPrebid.CurrencyConversions.ConversionRates)
+
+		// Create return adapters.ExtraRequestInfo object
+		reqInfo = adapters.NewExtraRequestInfo(conversions)
+	} else {
+		reqInfo = adapters.ExtraRequestInfo{}
+	}
+
+	// Set PbsEntryPoint if either isAmpTest or isVideoTest is true
 	if isAmpTest {
 		// simulates AMP entry point
 		reqInfo.PbsEntryPoint = "amp"
@@ -111,9 +146,7 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd
 		reqInfo.PbsEntryPoint = "video"
 	}
 
-	requests := testMakeRequestsImpl(t, filename, spec, bidder, &reqInfo)
-
-	testMakeBidsImpl(t, filename, spec, bidder, requests)
+	return &reqInfo
 }
 
 type testSpec struct {
diff --git a/adapters/impactify/impactifytest/exemplary/sample_banner.json b/adapters/impactify/impactifytest/exemplary/sample_banner.json
index f5d76c9e..2eeba02b 100644
--- a/adapters/impactify/impactifytest/exemplary/sample_banner.json
+++ b/adapters/impactify/impactifytest/exemplary/sample_banner.json
@@ -14,6 +14,8 @@
             }
           ]
         },
+        "bidfloor": 1.00,
+        "bidfloorcur": "MXN",
         "ext":{
           "bidder": {
             "appId": "impactify.io",
@@ -22,7 +24,19 @@
           }
         }
       }
-    ]
+    ],
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "MXN": {
+              "USD": 0.05
+            }
+          },
+          "usepbsrates": false
+        }
+      }
+    }
   },
 
   "httpCalls": [
@@ -45,6 +59,8 @@
                   }
                 ]
               },
+              "bidfloor": 0.05,
+              "bidfloorcur": "USD",
               "ext": {
                 "impactify": {
                   "appId": "impactify.io",
@@ -53,7 +69,19 @@
                 }
               }
             }
-          ]
+          ],
+          "ext": {
+            "prebid": {
+              "currency": {
+                "rates": {
+                  "MXN": {
+                    "USD": 0.05
+                  }
+                },
+                "usepbsrates": false
+              }
+            }
+          }
         }
       },
       "mockResponse": {
diff --git a/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json b/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json
new file mode 100644
index 00000000..d52ccd31
--- /dev/null
+++ b/adapters/impactify/impactifytest/supplemental/currency_rate_not_found.json
@@ -0,0 +1,45 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "bidfloor": 1.00,
+        "bidfloorcur": "JPY",
+        "ext":{
+          "bidder": {
+            "appId": "impactify.io",
+            "format": "screen",
+            "style": "impact"
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "MXN": {
+              "USD": 0.05
+            }
+          },
+          "usepbsrates": false
+        }
+      }
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Currency conversion rate not found: 'JPY' => 'USD'",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go
new file mode 100644
index 00000000..e09149a7
--- /dev/null
+++ b/adapters/openrtb_util_test.go
@@ -0,0 +1,117 @@
+package adapters
+
+import (
+	"encoding/json"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"reflect"
+	"testing"
+)
+
+func TestExtractAdapterReqBidderParams(t *testing.T) {
+	type args struct {
+		bidRequest *openrtb2.BidRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    map[string]json.RawMessage
+		wantErr bool
+	}{
+		{
+			name: "extract bidder params from nil req",
+			args: args{
+				bidRequest: nil,
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "extract bidder params from nil req.Ext",
+			args: args{
+				bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)},
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "extract bidder params from req.Ext for input request in adapter code",
+			args: args{
+				bidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"profile": 1234, "version": 1}}}`)},
+			},
+			want:    map[string]json.RawMessage{"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ExtractAdapterReqBidderParams(tt.args.bidRequest)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestExtractReqExtBidderParams(t *testing.T) {
+	type args struct {
+		request *openrtb2.BidRequest
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    map[string]map[string]json.RawMessage
+		wantErr bool
+	}{
+		{
+			name: "extract bidder params from nil req",
+			args: args{
+				request: nil,
+			},
+			want:    nil,
+			wantErr: true,
+		},
+		{
+			name: "extract bidder params from nil req.Ext.prebid",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)},
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "extract bidder params from nil req.Ext",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: nil},
+			},
+			want:    nil,
+			wantErr: false,
+		},
+		{
+			name: "extract bidder params from req.Ext for input request before adapter code",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile": 1234, "version": 1}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"} } }}}`)},
+			},
+			want: map[string]map[string]json.RawMessage{
+				"pubmatic": {"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)},
+				"appnexus": {"key1": json.RawMessage(`123`), "key2": json.RawMessage(`{"innerKey1":"innerValue1"}`)},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ExtractReqExtBidderParams(tt.args.request)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ExtractReqExtBidderParams() error = %v, wantErr = %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ExtractReqExtBidderParams() got = %v, want = %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json
new file mode 100644
index 00000000..06fc80ae
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/nilReqExt.json
@@ -0,0 +1,160 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adSlot": "AdTag_Div1@300x250",
+            "publisherId": "  999  ",
+            "keywords": [
+              {
+                "key": "pmZoneID",
+                "value": [
+                  "Zone1",
+                  "Zone2"
+                ]
+              },
+              {
+                "key": "preference",
+                "value": [
+                  "sports",
+                  "movies"
+                ]
+              }
+            ],
+            "wrapper": {
+              "version": 1,
+              "profile": 5123
+            }
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+    },
+    "ext": {},
+    "site": {
+      "id": "siteID",
+      "publisher": {
+        "id": "1234"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "AdTag_Div1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "h": 250,
+                "w": 300
+              },
+              "ext": {
+                "pmZoneId": "Zone1,Zone2",
+                "preference": "sports,movies"
+              }
+            }
+          ],
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+          },
+          "site": {
+            "id": "siteID",
+            "publisher": {
+              "id": "999"
+            }
+          },
+          "ext": {
+            "wrapper": {
+              "profile": 5123,
+              "version": 1
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [
+                {
+                  "id": "7706636740145184841",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": [
+                    "pubmatic.com"
+                  ],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid": "test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1
+                  }
+                }
+              ]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adid": "29681110",
+            "adm": "some-test-ad",
+            "adomain": [
+              "pubmatic.com"
+            ],
+            "crid": "29681110",
+            "w": 300,
+            "h": 250,
+            "dealid": "test deal",
+            "ext": {
+              "dspid": 6,
+              "deal_channel": 1
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json
new file mode 100644
index 00000000..5d4a1819
--- /dev/null
+++ b/adapters/pubmatic/pubmatictest/supplemental/reqBidderParams.json
@@ -0,0 +1,169 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adSlot": "AdTag_Div1@300x250",
+            "publisherId": "  999  ",
+            "keywords": [
+              {
+                "key": "pmZoneID",
+                "value": [
+                  "Zone1",
+                  "Zone2"
+                ]
+              },
+              {
+                "key": "preference",
+                "value": [
+                  "sports",
+                  "movies"
+                ]
+              }
+            ],
+            "wrapper": {
+              "version": 1,
+              "profile": 5123
+            }
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+    },
+    "ext": {
+      "prebid": {
+        "bidderparams": {
+          "wrapper": {
+            "profile": 1234,
+            "version": 2
+          }
+        }
+      }
+    },
+    "site": {
+      "id": "siteID",
+      "publisher": {
+        "id": "1234"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://hbopenbid.pubmatic.com/translator?source=prebid-server",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "AdTag_Div1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "h": 250,
+                "w": 300
+              },
+              "ext": {
+                "pmZoneId": "Zone1,Zone2",
+                "preference": "sports,movies"
+              }
+            }
+          ],
+          "device": {
+            "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
+          },
+          "site": {
+            "id": "siteID",
+            "publisher": {
+              "id": "999"
+            }
+          },
+          "ext": {
+            "wrapper": {
+              "profile": 1234,
+              "version": 2
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [
+                {
+                  "id": "7706636740145184841",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adid": "29681110",
+                  "adm": "some-test-ad",
+                  "adomain": [
+                    "pubmatic.com"
+                  ],
+                  "crid": "29681110",
+                  "h": 250,
+                  "w": 300,
+                  "dealid": "test deal",
+                  "ext": {
+                    "dspid": 6,
+                    "deal_channel": 1
+                  }
+                }
+              ]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adid": "29681110",
+            "adm": "some-test-ad",
+            "adomain": [
+              "pubmatic.com"
+            ],
+            "crid": "29681110",
+            "w": 300,
+            "h": 250,
+            "dealid": "test deal",
+            "ext": {
+              "dspid": 6,
+              "deal_channel": 1
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/videobyte/params_test.go b/adapters/videobyte/params_test.go
new file mode 100644
index 00000000..fa38836b
--- /dev/null
+++ b/adapters/videobyte/params_test.go
@@ -0,0 +1,55 @@
+package videobyte
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/videobyte.json
+//
+// These also validate the format of the external API: request.imp[i].ext.videobyte
+
+// TestValidParams makes sure that the videobyte 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.BidderVideoByte, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected videobyte params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the videobyte 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.BidderVideoByte, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"pubId": "123"}`,
+	`{"pubId": "123", "placementId": "4"}`,
+	`{"pubId": "123", "nid": "5"}`,
+	`{"pubId": "123", "placementId": "4", "nid": "5"}`,
+}
+
+var invalidParams = []string{
+	`{"invalidParam" : "a"}`,
+	`{}`,
+	`{"placementId": "4"}`,
+	`{"nid": "5"}`,
+	`{"placementId": "4", "nid": "5"}`,
+}
diff --git a/adapters/videobyte/videobyte_test.go b/adapters/videobyte/videobyte_test.go
new file mode 100644
index 00000000..9357a533
--- /dev/null
+++ b/adapters/videobyte/videobyte_test.go
@@ -0,0 +1,17 @@
+package videobyte
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/config"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder("videobyte", config.Adapter{Endpoint: "https://mock.videobyte.com"})
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+	adapterstest.RunJSONBidderTest(t, "videobytetest", bidder)
+}
diff --git a/adapters/videobyte/videobytetest/exemplary/banner.json b/adapters/videobyte/videobytetest/exemplary/banner.json
new file mode 100644
index 00000000..ecf733cc
--- /dev/null
+++ b/adapters/videobyte/videobytetest/exemplary/banner.json
@@ -0,0 +1,145 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-id",
+                  "adid": "564",
+                  "adm": "<iframe bordercolor=\"#000000\" frameborder=\"0\" height=\"250\" hspace=\"0\" marginheight=\"0\" marginwidth=\"0\" scrolling=\"no\" srcdoc=\"contents\" vspace=\"0\" width=\"300\"></iframe>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-id",
+            "adid": "564",
+            "adm": "<iframe bordercolor=\"#000000\" frameborder=\"0\" height=\"250\" hspace=\"0\" marginheight=\"0\" marginwidth=\"0\" scrolling=\"no\" srcdoc=\"contents\" vspace=\"0\" width=\"300\"></iframe>"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json
new file mode 100644
index 00000000..ddda5aa2
--- /dev/null
+++ b/adapters/videobyte/videobytetest/exemplary/empty-placement-network.json
@@ -0,0 +1,149 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?pid=examplePublisherId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-id",
+                  "adid": "564",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-id",
+            "adid": "564",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json
new file mode 100644
index 00000000..e10a4aa5
--- /dev/null
+++ b/adapters/videobyte/videobytetest/exemplary/empty-site-domain-ref.json
@@ -0,0 +1,143 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "page": "http://example.com/page-1"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "http://example.com/page-1"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-id",
+                  "adid": "564",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-id",
+            "adid": "564",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/exemplary/multi-format.json b/adapters/videobyte/videobytetest/exemplary/multi-format.json
new file mode 100644
index 00000000..cf62ad79
--- /dev/null
+++ b/adapters/videobyte/videobytetest/exemplary/multi-format.json
@@ -0,0 +1,161 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "video": {
+          "w": 300,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "video": {
+                "w": 300,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-id",
+                  "adid": "564",
+                  "adm": "<iframe bordercolor=\"#000000\" frameborder=\"0\" height=\"250\" hspace=\"0\" marginheight=\"0\" marginwidth=\"0\" scrolling=\"no\" srcdoc=\"contents\" vspace=\"0\" width=\"300\"></iframe>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-id",
+            "adid": "564",
+            "adm": "<iframe bordercolor=\"#000000\" frameborder=\"0\" height=\"250\" hspace=\"0\" marginheight=\"0\" marginwidth=\"0\" scrolling=\"no\" srcdoc=\"contents\" vspace=\"0\" width=\"300\"></iframe>"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/exemplary/multi-imp.json b/adapters/videobyte/videobytetest/exemplary/multi-imp.json
new file mode 100644
index 00000000..547a86da
--- /dev/null
+++ b/adapters/videobyte/videobytetest/exemplary/multi-imp.json
@@ -0,0 +1,277 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id-1",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId-1",
+            "placementId": "examplePlacementId-1",
+            "nid": "exampleNetworkId-1"
+          }
+        }
+      },
+      {
+        "id": "test-imp-id-2",
+        "video": {
+          "w": 800,
+          "h": 150,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId-2",
+            "placementId": "examplePlacementId-2",
+            "nid": "exampleNetworkId-2"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId-1&pid=examplePublisherId-1&placementId=examplePlacementId-1&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id-1",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId-1",
+                  "placementId": "examplePlacementId-1",
+                  "nid": "exampleNetworkId-1"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-id-1",
+                  "adid": "564",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    },
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId-2&pid=examplePublisherId-2&placementId=examplePlacementId-2&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id-2",
+              "video": {
+                "w": 800,
+                "h": 150,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId-2",
+                  "placementId": "examplePlacementId-2",
+                  "nid": "exampleNetworkId-2"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23c",
+                  "crid": "72746",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 4,
+                  "impid": "test-imp-id-2",
+                  "adid": "565",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-id-1",
+            "adid": "564",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    },
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23c",
+            "crid": "72746",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 4,
+            "impid": "test-imp-id-2",
+            "adid": "565",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/exemplary/video.json b/adapters/videobyte/videobytetest/exemplary/video.json
new file mode 100644
index 00000000..7cb07808
--- /dev/null
+++ b/adapters/videobyte/videobytetest/exemplary/video.json
@@ -0,0 +1,153 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+                  "crid": "72745",
+                  "adomain": [
+                    "ad-domain.com"
+                  ],
+                  "price": 3,
+                  "impid": "test-imp-id",
+                  "adid": "564",
+                  "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+                }
+              ],
+              "seat": "videobyte"
+            }
+          ],
+          "bidid": "test-request-id",
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "fe69dd6d-e85c-4186-80a7-605b530bc23b",
+            "crid": "72745",
+            "adomain": [
+              "ad-domain.com"
+            ],
+            "price": 3,
+            "impid": "test-imp-id",
+            "adid": "564",
+            "adm": "<?xml version=\"1.0\"?><VAST version=\"2.0\"></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json
new file mode 100644
index 00000000..1b806ed5
--- /dev/null
+++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext-bidder.json
@@ -0,0 +1,45 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": "invalid-bidder-ext"
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Ignoring imp id=test-imp-id, error while decoding impExt, err: json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpVideoByte",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json
new file mode 100644
index 00000000..199f4c55
--- /dev/null
+++ b/adapters/videobyte/videobytetest/supplemental/invalid-imp-ext.json
@@ -0,0 +1,43 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": "invalid-ext"
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Ignoring imp id=test-imp-id, error while decoding extImpBidder, err: json: cannot unmarshal string into Go value of type adapters.ExtImpBidder",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/supplemental/invalid-response.json b/adapters/videobyte/videobytetest/supplemental/invalid-response.json
new file mode 100644
index 00000000..35bce9a7
--- /dev/null
+++ b/adapters/videobyte/videobytetest/supplemental/invalid-response.json
@@ -0,0 +1,116 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+           "application/json"
+          ],
+          "Content-Type": [
+           "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": "invalid-body"
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Server Response",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000..09ad1bbb
--- /dev/null
+++ b/adapters/videobyte/videobytetest/supplemental/status-code-bad-request.json
@@ -0,0 +1,115 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+           "application/json"
+          ],
+          "Content-Type": [
+           "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 400
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad user input: HTTP status 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json
new file mode 100644
index 00000000..eaa61c74
--- /dev/null
+++ b/adapters/videobyte/videobytetest/supplemental/status-code-no-content.json
@@ -0,0 +1,111 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+           "application/json"
+          ],
+          "Content-Type": [
+           "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": []
+}
diff --git a/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000..d8a1c1f4
--- /dev/null
+++ b/adapters/videobyte/videobytetest/supplemental/status-code-other-error.json
@@ -0,0 +1,115 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "bcat": [
+      "IAB25",
+      "IAB7-39",
+      "IAB8-18"
+    ],
+    "user": {
+      "buyeruid": "user-101",
+      "yob": 1973
+    },
+    "device": {
+      "ua": "my-user-agent",
+      "ip": "1.2.3.4"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "pubId": "examplePublisherId",
+            "placementId": "examplePlacementId",
+            "nid": "exampleNetworkId"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "example.com",
+      "page": "http://example.com/page-1",
+      "ref": "http://referer.com/page-2"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "method": "GET",
+        "headers": {
+          "Referer": [
+            "http://referer.com/page-2"
+          ],
+          "Origin": [
+            "example.com"
+          ],
+          "Accept": [
+           "application/json"
+          ],
+          "Content-Type": [
+           "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://mock.videobyte.com?nid=exampleNetworkId&pid=examplePublisherId&placementId=examplePlacementId&source=pbs",
+        "body": {
+          "id": "test-request-id",
+          "bcat": [
+            "IAB25",
+            "IAB7-39",
+            "IAB8-18"
+          ],
+          "user": {
+            "buyeruid": "user-101",
+            "yob": 1973
+          },
+          "device": {
+            "ua": "my-user-agent",
+            "ip": "1.2.3.4"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "pubId": "examplePublisherId",
+                  "placementId": "examplePlacementId",
+                  "nid": "exampleNetworkId"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "example.com",
+            "page": "http://example.com/page-1",
+            "ref": "http://referer.com/page-2"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 505
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 505. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/currency/validation_test.go b/currency/validation_test.go
new file mode 100644
index 00000000..d49b9824
--- /dev/null
+++ b/currency/validation_test.go
@@ -0,0 +1,116 @@
+package currency
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestValidateCustomRates(t *testing.T) {
+	boolTrue := true
+	boolFalse := false
+
+	testCases := []struct {
+		desc               string
+		inBidReqCurrencies *openrtb_ext.ExtRequestCurrency
+		outCurrencyError   error
+	}{
+		{
+			desc:               "nil input, no errors expected",
+			inBidReqCurrencies: nil,
+			outCurrencyError:   nil,
+		},
+		{
+			desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{},
+				UsePBSRates:     &boolFalse,
+			},
+			outCurrencyError: nil,
+		},
+		{
+			desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{},
+				UsePBSRates:     &boolTrue,
+			},
+			outCurrencyError: nil,
+		},
+		{
+			desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{
+					"FOO": {
+						"GBP": 1.2,
+						"MXN": 0.05,
+						"JPY": 0.95,
+					},
+				},
+			},
+			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+		},
+		{
+			desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{
+					"FOO": {
+						"GBP": 1.2,
+						"MXN": 0.05,
+						"JPY": 0.95,
+					},
+				},
+				UsePBSRates: &boolFalse,
+			},
+			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+		},
+		{
+			desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{
+					"USD": {
+						"FOO": 10.0,
+						"MXN": 0.05,
+					},
+				},
+				UsePBSRates: &boolFalse,
+			},
+			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+		},
+		{
+			desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{
+					"FOO": {
+						"MXN": 0.05,
+						"CAD": 0.95,
+					},
+				},
+				UsePBSRates: &boolFalse,
+			},
+			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
+		},
+		{
+			desc: "All 3-digit currency codes exist, expect no error",
+			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
+				ConversionRates: map[string]map[string]float64{
+					"USD": {
+						"MXN": 0.05,
+					},
+					"MXN": {
+						"JPY": 10.0,
+						"EUR": 10.95,
+					},
+				},
+				UsePBSRates: &boolFalse,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		actualErr := ValidateCustomRates(tc.inBidReqCurrencies)
+
+		assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc)
+	}
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 8439cc65..a2f03289 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -1423,113 +1423,6 @@ func TestContentType(t *testing.T) {
 	}
 }
 
-func TestValidateCustomRates(t *testing.T) {
-	boolTrue := true
-	boolFalse := false
-
-	testCases := []struct {
-		desc               string
-		inBidReqCurrencies *openrtb_ext.ExtRequestCurrency
-		outCurrencyError   error
-	}{
-		{
-			desc:               "nil input, no errors expected",
-			inBidReqCurrencies: nil,
-			outCurrencyError:   nil,
-		},
-		{
-			desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{},
-				UsePBSRates:     &boolFalse,
-			},
-			outCurrencyError: nil,
-		},
-		{
-			desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{},
-				UsePBSRates:     &boolTrue,
-			},
-			outCurrencyError: nil,
-		},
-		{
-			desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{
-					"FOO": {
-						"GBP": 1.2,
-						"MXN": 0.05,
-						"JPY": 0.95,
-					},
-				},
-			},
-			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
-		},
-		{
-			desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{
-					"FOO": {
-						"GBP": 1.2,
-						"MXN": 0.05,
-						"JPY": 0.95,
-					},
-				},
-				UsePBSRates: &boolFalse,
-			},
-			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
-		},
-		{
-			desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{
-					"USD": {
-						"FOO": 10.0,
-						"MXN": 0.05,
-					},
-				},
-				UsePBSRates: &boolFalse,
-			},
-			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
-		},
-		{
-			desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{
-					"FOO": {
-						"MXN": 0.05,
-						"CAD": 0.95,
-					},
-				},
-				UsePBSRates: &boolFalse,
-			},
-			outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"},
-		},
-		{
-			desc: "All 3-digit currency codes exist, expect no error",
-			inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{
-				ConversionRates: map[string]map[string]float64{
-					"USD": {
-						"MXN": 0.05,
-					},
-					"MXN": {
-						"JPY": 10.0,
-						"EUR": 10.95,
-					},
-				},
-				UsePBSRates: &boolFalse,
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		actualErr := validateCustomRates(tc.inBidReqCurrencies)
-
-		assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc)
-	}
-}
-
 func TestValidateImpExt(t *testing.T) {
 	type testCase struct {
 		description    string
@@ -3915,3 +3808,117 @@ func TestValidateBanner(t *testing.T) {
 		assert.Equal(t, test.expectedError, result, test.description)
 	}
 }
+
+func TestParseRequestMergeBidderParams(t *testing.T) {
+	type args struct {
+		reqBody string
+	}
+	tests := []struct {
+		name           string
+		args           args
+		expectedImpExt json.RawMessage
+		expectedReqExt json.RawMessage
+		errL           int
+	}{
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params.json", "expectedReqExt"),
+			errL:           0,
+		},
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params-merge.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedReqExt"),
+			errL:           0,
+		},
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"),
+			errL:           0,
+		},
+		{
+			name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder for reserved keyword",
+			args: args{
+				reqBody: validRequest(t, "req-ext-bidder-params-merge-reserved-keyword.json"),
+			},
+			expectedImpExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedImpExt"),
+			expectedReqExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedReqExt"),
+			errL:           0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			deps := &endpointDeps{
+				fakeUUIDGenerator{},
+				&warningsCheckExchange{},
+				newParamsValidator(t),
+				&mockStoredReqFetcher{},
+				empty_fetcher.EmptyFetcher{},
+				empty_fetcher.EmptyFetcher{},
+				&config.Configuration{MaxRequestSize: int64(len(tt.args.reqBody))},
+				&metricsConfig.NilMetricsEngine{},
+				analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+				map[string]string{},
+				false,
+				[]byte{},
+				openrtb_ext.BuildBidderMap(),
+				nil,
+				nil,
+				hardcodedResponseIPValidator{response: true},
+			}
+
+			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(tt.args.reqBody))
+
+			resReq, _, errL := deps.parseRequest(req)
+
+			var expIExt, iExt map[string]interface{}
+			err := json.Unmarshal(tt.expectedImpExt, &expIExt)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil")
+			err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match")
+
+			var eReqE, reqE map[string]interface{}
+			err = json.Unmarshal(tt.expectedReqExt, &eReqE)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			err = json.Unmarshal(resReq.BidRequest.Ext, &reqE)
+			assert.Nil(t, err, "unmarshal() should return nil error")
+
+			assert.Equal(t, eReqE, reqE, "req.Ext should match")
+
+			assert.Len(t, errL, tt.errL, "error length should match")
+		})
+	}
+}
+
+func getObject(t *testing.T, filename, key string) json.RawMessage {
+	requestData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + filename)
+	if err != nil {
+		t.Fatalf("Failed to fetch a valid request: %v", err)
+	}
+	testBidRequest, _, _, err := jsonparser.Get(requestData, key)
+	assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err)
+
+	var obj json.RawMessage
+	err = json.Unmarshal(testBidRequest, &obj)
+	if err != nil {
+		t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err)
+	}
+	return obj
+}
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index ef7d0bdb..d0b4006d 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -5,12 +5,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/prebid/prebid-server/firstpartydata"
 	"testing"
 
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/firstpartydata"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -933,6 +933,90 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) {
 	}
 }
 
+func TestCleanOpenRTBRequestsBidderParams(t *testing.T) {
+	testCases := []struct {
+		description string
+		inExt       json.RawMessage
+		expectedExt map[string]json.RawMessage
+		hasError    bool
+	}{
+		{
+			description: "Nil Bidder params",
+			inExt:       nil,
+			expectedExt: getExpectedReqExt(true, false, false),
+			hasError:    false,
+		},
+		{
+			description: "Bidder params for single partner",
+			inExt:       json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}}}}`),
+			expectedExt: getExpectedReqExt(false, true, false),
+			hasError:    false,
+		},
+		{
+			description: "Bidder params for two partners",
+			inExt:       json.RawMessage(`{"prebid":{"bidderparams": {"pubmatic": {"profile":1234,"version":2}, "appnexus": {"key1": 123, "key2": {"innerKey1":"innerValue1"}} }}}`),
+			expectedExt: getExpectedReqExt(false, true, true),
+			hasError:    false,
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequestWithBidderParams(t)
+		var extRequest *openrtb_ext.ExtRequest
+		if test.inExt != nil {
+			req.Ext = test.inExt
+			unmarshaledExt, err := extractBidRequestExt(req)
+			assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt")
+			extRequest = unmarshaledExt
+		}
+
+		auctionReq := AuctionRequest{
+			BidRequest: req,
+			UserSyncs:  &emptyUsersync{},
+		}
+
+		bidderToSyncerKey := map[string]string{}
+		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+		metrics := metrics.MetricsEngineMock{}
+		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+		if test.hasError == true {
+			assert.NotNil(t, errs)
+			assert.Len(t, bidderRequests, 0)
+		} else {
+			assert.Nil(t, errs)
+			for _, r := range bidderRequests {
+				expected := test.expectedExt[r.BidderName.String()]
+				actual := r.BidRequest.Ext
+				assert.Equal(t, expected, actual, test.description+" Req:Ext.Prebid.BidderParams")
+			}
+		}
+	}
+}
+
+func getExpectedReqExt(nilExt, includePubmaticParams, includeAppnexusParams bool) map[string]json.RawMessage {
+	bidderParamsMap := make(map[string]json.RawMessage)
+
+	if nilExt {
+		bidderParamsMap["pubmatic"] = json.RawMessage(``)
+		bidderParamsMap["appnexus"] = json.RawMessage(``)
+		return bidderParamsMap
+	}
+
+	if includePubmaticParams {
+		bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{"bidderparams":{"profile":1234,"version":2}}}`)
+	} else {
+		bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{}}`)
+	}
+
+	if includeAppnexusParams {
+		bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{"bidderparams":{"key1":123,"key2":{"innerKey1":"innerValue1"}}}}`)
+	} else {
+		bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{}}`)
+	}
+
+	return bidderParamsMap
+}
+
 func TestExtractBidRequestExt(t *testing.T) {
 	var boolFalse, boolTrue *bool = new(bool), new(bool)
 	*boolFalse = false
@@ -2000,6 +2084,47 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest {
 	}
 }
 
+func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest {
+	return &openrtb2.BidRequest{
+		Site: &openrtb2.Site{
+			Page:   "www.some.domain.com",
+			Domain: "domain.com",
+			Publisher: &openrtb2.Publisher{
+				ID: "some-publisher-id",
+			},
+		},
+		Device: &openrtb2.Device{
+			DIDMD5:   "some device ID hash",
+			UA:       "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
+			IFA:      "ifa",
+			IP:       "132.173.230.74",
+			Language: "EN",
+		},
+		Source: &openrtb2.Source{
+			TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87",
+		},
+		User: &openrtb2.User{
+			ID:       "our-id",
+			BuyerUID: "their-id",
+			Yob:      1982,
+			Ext:      json.RawMessage(`{}`),
+		},
+		Imp: []openrtb2.Imp{{
+			ID: "some-imp-id",
+			Banner: &openrtb2.Banner{
+				Format: []openrtb2.Format{{
+					W: 300,
+					H: 250,
+				}, {
+					W: 300,
+					H: 600,
+				}},
+			},
+			Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}`),
+		}},
+	}
+}
+
 func TestRandomizeList(t *testing.T) {
 	var (
 		bidder1 = openrtb_ext.BidderName("bidder1")
@@ -2609,8 +2734,87 @@ func TestBuildXPrebidHeader(t *testing.T) {
 	}
 }
 
-func TestApplyFPD(t *testing.T) {
+func TestSourceExtSChainCopied(t *testing.T) {
+	bidRequest := newBidRequest(t)
 
+	bidderSchains := map[string]*openrtb_ext.ExtRequestPrebidSChainSChain{
+		"bidder1": {
+			Ver:      "1.0",
+			Complete: 1,
+			Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+				{
+					ASI: "bidder1.com",
+					SID: "0001",
+					HP:  1,
+				},
+			},
+		},
+		"bidder2": {
+			Ver:      "1.0",
+			Complete: 1,
+			Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{
+				{
+					ASI: "bidder2.com",
+					SID: "0002",
+					HP:  1,
+				},
+			},
+		},
+	}
+
+	copy1 := *bidRequest
+	originalTid := copy1.Source.TID
+	prepareSource(&copy1, "bidder1", bidderSchains)
+	copy2 := *bidRequest
+	copy2.Source.TID = "new TID"
+	prepareSource(&copy2, "bidder2", bidderSchains)
+
+	assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder1.com","sid":"0001","hp":1}],"ver":"1.0"}}`), copy1.Source.Ext, "First schain was overwritten or not set")
+	assert.Equal(t, originalTid, copy1.Source.TID, "Original TID was overwritten")
+	assert.Equal(t, json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"bidder2.com","sid":"0002","hp":1}],"ver":"1.0"}}`), copy2.Source.Ext, "Second schain was overwritten or not set")
+	assert.Equal(t, "new TID", copy2.Source.TID, "New TID was not set")
+}
+
+func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) {
+	req := &openrtb2.BidRequest{
+		Site: &openrtb2.Site{},
+		Source: &openrtb2.Source{
+			TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87",
+		},
+		Imp: []openrtb2.Imp{{
+			Ext: json.RawMessage(`{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}`),
+		}},
+		Ext: json.RawMessage(`{"prebid":{"schains":[{ "bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["axonix"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
+	}
+
+	unmarshaledExt, err := extractBidRequestExt(req)
+	assert.NoErrorf(t, err, "Error unmarshaling inExt")
+	extRequest := unmarshaledExt
+
+	auctionReq := AuctionRequest{
+		BidRequest: req,
+		UserSyncs:  &emptyUsersync{},
+	}
+	bidderToSyncerKey := map[string]string{}
+	permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
+	metrics := metrics.MetricsEngineMock{}
+	bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+
+	assert.Nil(t, errs)
+	assert.Len(t, bidderRequests, 2, "Bid request count is not 2")
+
+	bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{}
+	for _, bidderRequest := range bidderRequests {
+		bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext
+	}
+
+	appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`)
+	axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`)
+	assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext")
+	assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext")
+}
+
+func TestApplyFPD(t *testing.T) {
 	fpdBidderTest := map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{}
 	bidderTest := openrtb_ext.BidderName("test")
 	fpdBidderTest[bidderTest] = &firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil}
diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go
index 26ebf7dc..8bd25d9e 100644
--- a/openrtb_ext/bidders_test.go
+++ b/openrtb_ext/bidders_test.go
@@ -110,6 +110,9 @@ func TestIsBidderNameReserved(t *testing.T) {
 		{"prebid", true},
 		{"PREbid", true},
 		{"PREBID", true},
+		{"bidder", true},
+		{"BIDDER", true},
+		{"BidDer", true},
 		{"notreserved", false},
 	}