Repository: prebid/prebid-server
Pull request #2741 (2023-05-03 07:07:24)

New adapter: Adsyield
Docs: https://github.com/prebid/prebid.github.io/pull/4533


Patch:
diff --git a/account/account.go b/account/account.go
index a32423d1..052d0199 100644
--- a/account/account.go
+++ b/account/account.go
@@ -102,6 +102,10 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r
 		})
 		return nil, errs
 	}
+
+	// set the value of events.enabled field based on deprecated events_enabled field and ensure backward compatibility
+	deprecateEventsEnabledField(account)
+
 	return account, nil
 }
 
@@ -252,3 +256,16 @@ func useGDPRChannelEnabled(account *config.Account) bool {
 func useCCPAChannelEnabled(account *config.Account) bool {
 	return account.CCPA.ChannelEnabled.IsSet() && !account.CCPA.IntegrationEnabled.IsSet()
 }
+
+// deprecateEventsEnabledField is responsible for ensuring backwards compatibility of "events_enabled" field.
+// This function favors "events.enabled" field over deprecated "events_enabled" field, if values for both are set.
+// If only deprecated "events_enabled" field is set then it sets the same value to "events.enabled" field.
+func deprecateEventsEnabledField(account *config.Account) {
+	if account != nil {
+		if account.Events.Enabled == nil {
+			account.Events.Enabled = account.EventsEnabled
+		}
+		// assign the old value to the new value so old and new are always the same even though the new value is what is used in the application code.
+		account.EventsEnabled = account.Events.Enabled
+	}
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 4b4fbff7..8084b2bc 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"math"
 	"net/http"
 	"strconv"
 	"strings"
@@ -283,8 +284,8 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP
 	if pubmaticExt.Kadfloor != "" {
 		bidfloor, err := strconv.ParseFloat(strings.TrimSpace(pubmaticExt.Kadfloor), 64)
 		if err == nil {
-			//do not overwrite existing value if kadfloor is invalid
-			imp.BidFloor = bidfloor
+			// In case of valid kadfloor, select maximum of original imp.bidfloor and kadfloor
+			imp.BidFloor = math.Max(bidfloor, imp.BidFloor)
 		}
 	}
 
diff --git a/bidadjustment/apply.go b/bidadjustment/apply.go
new file mode 100644
index 00000000..4fa3b737
--- /dev/null
+++ b/bidadjustment/apply.go
@@ -0,0 +1,94 @@
+package bidadjustment
+
+import (
+	"math"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+const (
+	AdjustmentTypeCPM        = "cpm"
+	AdjustmentTypeMultiplier = "multiplier"
+	AdjustmentTypeStatic     = "static"
+	WildCard                 = "*"
+	Delimiter                = "|"
+)
+
+const maxNumOfCombos = 8
+const pricePrecision float64 = 10000 // Rounds to 4 Decimal Places
+const minBid = 0.1
+
+// Apply gets the highest priority adjustment slice given a map of rules, and applies those adjustments to a bid's price
+func Apply(rules map[string][]openrtb_ext.Adjustment, bidInfo *adapters.TypedBid, bidderName openrtb_ext.BidderName, currency string, reqInfo *adapters.ExtraRequestInfo, bidType string) (float64, string) {
+	adjustments := []openrtb_ext.Adjustment{}
+	if len(rules) > 0 {
+		adjustments = get(rules, bidType, string(bidderName), bidInfo.Bid.DealID)
+	} else {
+		return bidInfo.Bid.Price, currency
+	}
+	adjustedPrice, adjustedCurrency := apply(adjustments, bidInfo.Bid.Price, currency, reqInfo)
+
+	if bidInfo.Bid.DealID != "" && adjustedPrice < 0 {
+		return 0, currency
+	}
+	if bidInfo.Bid.DealID == "" && adjustedPrice <= 0 {
+		return minBid, currency
+	}
+	return adjustedPrice, adjustedCurrency
+}
+
+func apply(adjustments []openrtb_ext.Adjustment, bidPrice float64, currency string, reqInfo *adapters.ExtraRequestInfo) (float64, string) {
+	if len(adjustments) == 0 {
+		return bidPrice, currency
+	}
+	originalBidPrice := bidPrice
+
+	for _, adjustment := range adjustments {
+		switch adjustment.Type {
+		case AdjustmentTypeMultiplier:
+			bidPrice = bidPrice * adjustment.Value
+		case AdjustmentTypeCPM:
+			convertedVal, err := reqInfo.ConvertCurrency(adjustment.Value, adjustment.Currency, currency)
+			if err != nil {
+				return originalBidPrice, currency
+			}
+			bidPrice = bidPrice - convertedVal
+		case AdjustmentTypeStatic:
+			bidPrice = adjustment.Value
+			currency = adjustment.Currency
+		}
+	}
+	roundedBidPrice := math.Round(bidPrice*pricePrecision) / pricePrecision
+
+	return roundedBidPrice, currency
+}
+
+// get() should return the highest priority slice of adjustments from the map that we can match with the given bid info
+// given the bid info, we create the same format of combinations that's present in the key of the ruleToAdjustments map
+// the slice is ordered by priority from highest to lowest, as soon as we find a match, we return that slice
+func get(rules map[string][]openrtb_ext.Adjustment, bidType, bidderName, dealID string) []openrtb_ext.Adjustment {
+	priorityRules := [maxNumOfCombos]string{}
+	if dealID != "" {
+		priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + dealID
+		priorityRules[1] = bidType + Delimiter + bidderName + Delimiter + WildCard
+		priorityRules[2] = bidType + Delimiter + WildCard + Delimiter + dealID
+		priorityRules[3] = WildCard + Delimiter + bidderName + Delimiter + dealID
+		priorityRules[4] = bidType + Delimiter + WildCard + Delimiter + WildCard
+		priorityRules[5] = WildCard + Delimiter + bidderName + Delimiter + WildCard
+		priorityRules[6] = WildCard + Delimiter + WildCard + Delimiter + dealID
+		priorityRules[7] = WildCard + Delimiter + WildCard + Delimiter + WildCard
+	} else {
+		priorityRules[0] = bidType + Delimiter + bidderName + Delimiter + WildCard
+		priorityRules[1] = bidType + Delimiter + WildCard + Delimiter + WildCard
+		priorityRules[2] = WildCard + Delimiter + bidderName + Delimiter + WildCard
+		priorityRules[3] = WildCard + Delimiter + WildCard + Delimiter + WildCard
+	}
+
+	for _, rule := range priorityRules {
+		if _, ok := rules[rule]; ok {
+			return rules[rule]
+		}
+	}
+	return nil
+}
diff --git a/bidadjustment/build_rules.go b/bidadjustment/build_rules.go
new file mode 100644
index 00000000..bccb3bc8
--- /dev/null
+++ b/bidadjustment/build_rules.go
@@ -0,0 +1,103 @@
+package bidadjustment
+
+import (
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+const (
+	VideoInstream  = "video-instream"
+	VideoOutstream = "video-outstream"
+)
+
+// BuildRules() will populate the rules map with a rule that's a combination of the mediaType, bidderName, and dealId for a particular adjustment
+// The result will be a map that'll map a given rule with its adjustment
+func BuildRules(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) map[string][]openrtb_ext.Adjustment {
+	if bidAdjustments == nil {
+		return nil
+	}
+	rules := make(map[string][]openrtb_ext.Adjustment)
+
+	buildRulesForMediaType(string(openrtb_ext.BidTypeBanner), bidAdjustments.MediaType.Banner, rules)
+	buildRulesForMediaType(string(openrtb_ext.BidTypeAudio), bidAdjustments.MediaType.Audio, rules)
+	buildRulesForMediaType(string(openrtb_ext.BidTypeNative), bidAdjustments.MediaType.Native, rules)
+	buildRulesForMediaType(VideoInstream, bidAdjustments.MediaType.VideoInstream, rules)
+	buildRulesForMediaType(VideoOutstream, bidAdjustments.MediaType.VideoOutstream, rules)
+	buildRulesForMediaType(WildCard, bidAdjustments.MediaType.WildCard, rules)
+
+	return rules
+}
+
+func buildRulesForMediaType(mediaType string, rulesByBidder map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID, rules map[string][]openrtb_ext.Adjustment) {
+	for bidderName := range rulesByBidder {
+		for dealID, adjustments := range rulesByBidder[bidderName] {
+			rule := mediaType + Delimiter + string(bidderName) + Delimiter + dealID
+			rules[rule] = adjustments
+		}
+	}
+}
+
+// Merge takes bid adjustments defined on the request and on the account, and combines/validates them, with the adjustments on the request taking precedence.
+func Merge(req *openrtb_ext.RequestWrapper, acctBidAdjs *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) {
+	mergedBidAdj, err := merge(req, acctBidAdjs)
+	if err != nil {
+		return nil, err
+	}
+	if !Validate(mergedBidAdj) {
+		mergedBidAdj = nil
+		err = &errortypes.Warning{
+			WarningCode: errortypes.BidAdjustmentWarningCode,
+			Message:     "bid adjustment on account was invalid",
+		}
+	}
+	return mergedBidAdj, err
+}
+
+func merge(req *openrtb_ext.RequestWrapper, acct *openrtb_ext.ExtRequestPrebidBidAdjustments) (*openrtb_ext.ExtRequestPrebidBidAdjustments, error) {
+	reqExt, err := req.GetRequestExt()
+	if err != nil {
+		return nil, err
+	}
+	extPrebid := reqExt.GetPrebid()
+
+	if extPrebid == nil && acct == nil {
+		return nil, nil
+	}
+	if extPrebid == nil && acct != nil {
+		return acct, nil
+	}
+	if extPrebid != nil && acct == nil {
+		return extPrebid.BidAdjustments, nil
+	}
+
+	extPrebid.BidAdjustments.MediaType.Banner = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Banner, acct.MediaType.Banner)
+	extPrebid.BidAdjustments.MediaType.Native = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Native, acct.MediaType.Native)
+	extPrebid.BidAdjustments.MediaType.Audio = mergeForMediaType(extPrebid.BidAdjustments.MediaType.Audio, acct.MediaType.Audio)
+	extPrebid.BidAdjustments.MediaType.VideoInstream = mergeForMediaType(extPrebid.BidAdjustments.MediaType.VideoInstream, acct.MediaType.VideoInstream)
+	extPrebid.BidAdjustments.MediaType.VideoOutstream = mergeForMediaType(extPrebid.BidAdjustments.MediaType.VideoOutstream, acct.MediaType.VideoOutstream)
+	extPrebid.BidAdjustments.MediaType.WildCard = mergeForMediaType(extPrebid.BidAdjustments.MediaType.WildCard, acct.MediaType.WildCard)
+
+	return extPrebid.BidAdjustments, nil
+}
+
+func mergeForMediaType(reqAdj, acctAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID {
+	if reqAdj != nil && acctAdj == nil {
+		return reqAdj
+	}
+	if reqAdj == nil && acctAdj != nil {
+		return acctAdj
+	}
+
+	for bidderName, dealIDToAdjustments := range acctAdj {
+		if _, ok := reqAdj[bidderName]; ok {
+			for dealID, acctAdjustments := range acctAdj[bidderName] {
+				if _, ok := reqAdj[bidderName][dealID]; !ok {
+					reqAdj[bidderName][dealID] = acctAdjustments
+				}
+			}
+		} else {
+			reqAdj[bidderName] = dealIDToAdjustments
+		}
+	}
+	return reqAdj
+}
diff --git a/bidadjustment/validate.go b/bidadjustment/validate.go
new file mode 100644
index 00000000..c0ae3d4a
--- /dev/null
+++ b/bidadjustment/validate.go
@@ -0,0 +1,70 @@
+package bidadjustment
+
+import (
+	"math"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// Validate checks whether all provided bid adjustments are valid or not against the requirements defined in the issue
+func Validate(bidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments) bool {
+	if bidAdjustments == nil {
+		return true
+	}
+	if !validateForMediaType(bidAdjustments.MediaType.Banner) {
+		return false
+	}
+	if !validateForMediaType(bidAdjustments.MediaType.Audio) {
+		return false
+	}
+	if !validateForMediaType(bidAdjustments.MediaType.VideoInstream) {
+		return false
+	}
+	if !validateForMediaType(bidAdjustments.MediaType.VideoOutstream) {
+		return false
+	}
+	if !validateForMediaType(bidAdjustments.MediaType.Native) {
+		return false
+	}
+	if !validateForMediaType(bidAdjustments.MediaType.WildCard) {
+		return false
+	}
+	return true
+}
+
+func validateForMediaType(bidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID) bool {
+	for bidderName := range bidAdj {
+		if bidAdj[bidderName] == nil {
+			return false
+		}
+		for dealID := range bidAdj[bidderName] {
+			if bidAdj[bidderName][dealID] == nil {
+				return false
+			}
+			for _, adjustment := range bidAdj[bidderName][dealID] {
+				if !validateAdjustment(adjustment) {
+					return false
+				}
+			}
+		}
+	}
+	return true
+}
+
+func validateAdjustment(adjustment openrtb_ext.Adjustment) bool {
+	switch adjustment.Type {
+	case AdjustmentTypeCPM:
+		if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 {
+			return true
+		}
+	case AdjustmentTypeMultiplier:
+		if adjustment.Value >= 0 && adjustment.Value < 100 {
+			return true
+		}
+	case AdjustmentTypeStatic:
+		if adjustment.Currency != "" && adjustment.Value >= 0 && adjustment.Value < math.MaxFloat64 {
+			return true
+		}
+	}
+	return false
+}
diff --git a/config/accounts.go b/config/accounts.go
index 77803309..f96eb53a 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -23,22 +23,23 @@ const (
 
 // Account represents a publisher account configuration
 type Account struct {
-	ID                      string                               `mapstructure:"id" json:"id"`
-	Disabled                bool                                 `mapstructure:"disabled" json:"disabled"`
-	CacheTTL                DefaultTTLs                          `mapstructure:"cache_ttl" json:"cache_ttl"`
-	EventsEnabled           bool                                 `mapstructure:"events_enabled" json:"events_enabled"`
-	CCPA                    AccountCCPA                          `mapstructure:"ccpa" json:"ccpa"`
-	GDPR                    AccountGDPR                          `mapstructure:"gdpr" json:"gdpr"`
-	DebugAllow              bool                                 `mapstructure:"debug_allow" json:"debug_allow"`
-	DefaultIntegration      string                               `mapstructure:"default_integration" json:"default_integration"`
-	CookieSync              CookieSync                           `mapstructure:"cookie_sync" json:"cookie_sync"`
-	Events                  Events                               `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
-	TruncateTargetAttribute *int                                 `mapstructure:"truncate_target_attr" json:"truncate_target_attr"`
-	AlternateBidderCodes    *openrtb_ext.ExtAlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"`
-	Hooks                   AccountHooks                         `mapstructure:"hooks" json:"hooks"`
-	PriceFloors             AccountPriceFloors                   `mapstructure:"price_floors" json:"price_floors"`
-	Validations             Validations                          `mapstructure:"validations" json:"validations"`
-	DefaultBidLimit         int                                  `mapstructure:"default_bid_limit" json:"default_bid_limit"`
+	ID                      string                                      `mapstructure:"id" json:"id"`
+	Disabled                bool                                        `mapstructure:"disabled" json:"disabled"`
+	CacheTTL                DefaultTTLs                                 `mapstructure:"cache_ttl" json:"cache_ttl"`
+	EventsEnabled           *bool                                       `mapstructure:"events_enabled" json:"events_enabled"` // Deprecated: Use events.enabled instead.
+	CCPA                    AccountCCPA                                 `mapstructure:"ccpa" json:"ccpa"`
+	GDPR                    AccountGDPR                                 `mapstructure:"gdpr" json:"gdpr"`
+	DebugAllow              bool                                        `mapstructure:"debug_allow" json:"debug_allow"`
+	DefaultIntegration      string                                      `mapstructure:"default_integration" json:"default_integration"`
+	CookieSync              CookieSync                                  `mapstructure:"cookie_sync" json:"cookie_sync"`
+	Events                  Events                                      `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
+	TruncateTargetAttribute *int                                        `mapstructure:"truncate_target_attr" json:"truncate_target_attr"`
+	AlternateBidderCodes    *openrtb_ext.ExtAlternateBidderCodes        `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"`
+	Hooks                   AccountHooks                                `mapstructure:"hooks" json:"hooks"`
+	PriceFloors             AccountPriceFloors                          `mapstructure:"price_floors" json:"price_floors"`
+	Validations             Validations                                 `mapstructure:"validations" json:"validations"`
+	DefaultBidLimit         int                                         `mapstructure:"default_bid_limit" json:"default_bid_limit"`
+	BidAdjustments          *openrtb_ext.ExtRequestPrebidBidAdjustments `mapstructure:"bidadjustments" json:"bidadjustments"`
 }
 
 // CookieSync represents the account-level defaults for the cookie sync endpoint.
diff --git a/config/config.go b/config/config.go
index e1d27064..d70afc21 100644
--- a/config/config.go
+++ b/config/config.go
@@ -17,6 +17,7 @@ import (
 	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/util/ptrutil"
 )
 
 // Configuration specifies the static application config.
@@ -138,14 +139,15 @@ func (cfg *Configuration) validate(v *viper.Viper) []error {
 	if cfg.AccountDefaults.Disabled {
 		glog.Warning(`With account_defaults.disabled=true, host-defined accounts must exist and have "disabled":false. All other requests will be rejected.`)
 	}
-	if cfg.AccountDefaults.Events.Enabled {
-		glog.Warning(`account_defaults.events will currently not do anything as the feature is still under development. Please follow https://github.com/prebid/prebid-server/issues/1725 for more updates`)
-	}
 
 	if cfg.PriceFloors.Enabled {
 		glog.Warning(`cfg.PriceFloors.Enabled will currently not do anything as price floors feature is still under development.`)
 	}
 
+	if len(cfg.AccountDefaults.Events.VASTEvents) > 0 {
+		errs = append(errs, errors.New("account_defaults.Events.VASTEvents has no effect as the feature is under development."))
+	}
+
 	errs = cfg.Experiment.validate(errs)
 	errs = cfg.BidderInfos.validate(errs)
 	return errs
@@ -688,6 +690,10 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin
 
 	// Update account defaults and generate base json for patch
 	c.AccountDefaults.CacheTTL = c.CacheURL.DefaultTTLs // comment this out to set explicitly in config
+
+	// Update the deprecated and new events enabled values for account defaults.
+	c.AccountDefaults.EventsEnabled, c.AccountDefaults.Events.Enabled = migrateConfigEventsEnabled(c.AccountDefaults.EventsEnabled, c.AccountDefaults.Events.Enabled)
+
 	if err := c.MarshalAccountDefaults(); err != nil {
 		return nil, err
 	}
@@ -1008,6 +1014,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
 	v.SetDefault("account_defaults.price_floors.use_dynamic_data", false)
 	v.SetDefault("account_defaults.price_floors.max_rules", 100)
 	v.SetDefault("account_defaults.price_floors.max_schema_dims", 3)
+	v.SetDefault("account_defaults.events_enabled", false)
 
 	v.SetDefault("certificates_file", "")
 	v.SetDefault("auto_gen_source_tid", true)
@@ -1371,6 +1378,28 @@ func migrateConfigDatabaseConnection(v *viper.Viper) {
 	}
 }
 
+// migrateConfigEventsEnabled is responsible for ensuring backward compatibility of events_enabled field.
+// This function copies the value of newField "events.enabled" and set it to the oldField "events_enabled".
+// This is necessary to achieve the desired order of precedence favoring the account values over the host values
+// given the account fetcher JSON merge mechanics.
+func migrateConfigEventsEnabled(oldFieldValue *bool, newFieldValue *bool) (updatedOldFieldValue, updatedNewFieldValue *bool) {
+	newField := "account_defaults.events.enabled"
+	oldField := "account_defaults.events_enabled"
+
+	updatedOldFieldValue = oldFieldValue
+	if oldFieldValue != nil {
+		glog.Warningf("%s is deprecated and should be changed to %s", oldField, newField)
+	}
+	if newFieldValue != nil {
+		if oldFieldValue != nil {
+			glog.Warningf("using %s and ignoring deprecated %s", newField, oldField)
+		}
+		updatedOldFieldValue = ptrutil.ToPtr(*newFieldValue)
+	}
+
+	return updatedOldFieldValue, nil
+}
+
 func isConfigInfoPresent(v *viper.Viper, prefix string, fields []string) bool {
 	prefix = prefix + "."
 	for _, field := range fields {
diff --git a/config/events.go b/config/events.go
index cf3139d8..83d2df4b 100644
--- a/config/events.go
+++ b/config/events.go
@@ -61,14 +61,14 @@ type VASTEvent struct {
 // within the VAST XML
 // Don't enable this feature. It is still under developmment. Please follow https://github.com/prebid/prebid-server/issues/1725 for more updates
 type Events struct {
-	Enabled    bool        `mapstructure:"enabled" json:"enabled"`
+	Enabled    *bool       `mapstructure:"enabled" json:"enabled"`
 	DefaultURL string      `mapstructure:"default_url" json:"default_url"`
 	VASTEvents []VASTEvent `mapstructure:"vast_events" json:"vast_events,omitempty"`
 }
 
 // validate verifies the events object  and returns error if at least one is invalid.
 func (e Events) validate(errs []error) []error {
-	if e.Enabled {
+	if e.IsEnabled() {
 		if !isValidURL(e.DefaultURL) {
 			return append(errs, errors.New("Invalid events.default_url"))
 		}
@@ -147,3 +147,8 @@ func isValidURL(eventURL string) bool {
 func (e VASTEvent) isTrackingEvent() bool {
 	return e.CreateElement == TrackingVASTElement
 }
+
+// IsEnabled function returns the value of events.enabled field
+func (e Events) IsEnabled() bool {
+	return e.Enabled != nil && *e.Enabled
+}
diff --git a/endpoints/events/event.go b/endpoints/events/event.go
index ca323a61..ba77f3ce 100644
--- a/endpoints/events/event.go
+++ b/endpoints/events/event.go
@@ -107,8 +107,8 @@ func (e *eventEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprou
 		return
 	}
 
-	// account does not support events
-	if !account.EventsEnabled {
+	// Check if events are enabled for the account
+	if !account.Events.IsEnabled() {
 		w.WriteHeader(http.StatusUnauthorized)
 		w.Write([]byte(fmt.Sprintf("Account '%s' doesn't support events", eventRequest.AccountID)))
 		return
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 2811a9e7..7f361720 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -25,6 +25,7 @@ import (
 	"github.com/prebid/openrtb/v19/openrtb2"
 	"github.com/prebid/openrtb/v19/openrtb3"
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/bidadjustment"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/ortb"
 	"golang.org/x/net/publicsuffix"
@@ -723,7 +724,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 	}
 
 	if errs := validateRequestExt(req); len(errs) != 0 {
-		return append(errL, errs...)
+		if errortypes.ContainsFatalError(errs) {
+			return append(errL, errs...)
+		}
+		errL = append(errL, errs...)
 	}
 
 	if err := deps.validateSite(req); err != nil {
@@ -1554,6 +1558,15 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error {
 		reqExt.SetPrebid(prebid)
 	}
 
+	if !bidadjustment.Validate(prebid.BidAdjustments) {
+		prebid.BidAdjustments = nil
+		reqExt.SetPrebid(prebid)
+		errs = append(errs, &errortypes.Warning{
+			WarningCode: errortypes.BidAdjustmentWarningCode,
+			Message:     "bid adjustment from request was invalid",
+		})
+	}
+
 	return errs
 }
 
diff --git a/endpoints/openrtb2/sample-requests/bidadjustments/cpm.json b/endpoints/openrtb2/sample-requests/bidadjustments/cpm.json
new file mode 100644
index 00000000..db0a06e6
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/bidadjustments/cpm.json
@@ -0,0 +1,105 @@
+{
+  "description": "Bid Adjustment Test with CPM, and WildCard Preference Testing",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 20.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "EUR": {
+              "USD": 2.0
+            }
+          },
+          "usepbsrates": false
+        },
+        "bidadjustments": {
+          "mediatype": {
+            "banner": {
+              "appnexus": {
+                "*": [
+                  {
+                    "adjtype": "cpm",
+                    "value": 5.0,
+                    "currency": "EUR"
+                  }
+                ]
+              },
+              "*": {
+                "*": [
+                  {
+                    "adjtype": "multiplier",
+                    "value": 3.0
+                  }
+                ]
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 10.0,
+            "ext": {
+              "origbidcpm": 20,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/bidadjustments/invalid.json b/endpoints/openrtb2/sample-requests/bidadjustments/invalid.json
new file mode 100644
index 00000000..cbd5f758
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/bidadjustments/invalid.json
@@ -0,0 +1,90 @@
+{
+  "description": "Bid Adjustment Test With Invalid Adjustment",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 2.00,
+        "dealid": "some-deal-id"
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "bidadjustments": {
+          "mediatype": {
+            "banner": {
+              "appnexus": {
+                "some-deal-id": [
+                  {
+                    "adjtype": "multiplier",
+                    "value": -2.0
+                  }
+                ]
+              }
+            }
+          }
+        },
+        "debug": true
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 2.0,
+            "ext": {
+              "origbidcpm": 2,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/bidadjustments/multiplier.json b/endpoints/openrtb2/sample-requests/bidadjustments/multiplier.json
new file mode 100644
index 00000000..72a8dca9
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/bidadjustments/multiplier.json
@@ -0,0 +1,89 @@
+{
+  "description": "Bid Adjustment Test With One Adjustment",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 2.00,
+        "dealid": "some-deal-id"
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "bidadjustments": {
+          "mediatype": {
+            "banner": {
+              "appnexus": {
+                "some-deal-id": [
+                  {
+                    "adjtype": "multiplier",
+                    "value": 2.0
+                  }
+                ]
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 4.0,
+            "ext": {
+              "origbidcpm": 2,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/bidadjustments/static.json b/endpoints/openrtb2/sample-requests/bidadjustments/static.json
new file mode 100644
index 00000000..9969c47e
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/bidadjustments/static.json
@@ -0,0 +1,106 @@
+{
+  "description": "Bid Adjustment with Static and WildCard testing",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 2.00,
+        "dealid": "some-deal-id"
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "EUR": {
+              "USD": 2.0
+            }
+          },
+          "usepbsrates": false
+        },
+        "bidadjustments": {
+          "mediatype": {
+            "banner": {
+              "*": {
+                "some-deal-id": [
+                  {
+                    "adjtype": "static",
+                    "value": 10.0,
+                    "currency": "EUR"
+                  }
+                ]
+              },
+              "some-bidder-name": {
+                "*": [
+                  {
+                    "adjtype": "multiplier",
+                    "value": 2.0
+                  }
+                ]
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 10.0,
+            "ext": {
+              "origbidcpm": 2,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "EUR",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/errortypes/code.go b/errortypes/code.go
index f81ee464..99afb02a 100644
--- a/errortypes/code.go
+++ b/errortypes/code.go
@@ -26,6 +26,7 @@ const (
 	AlternateBidderCodeWarningCode
 	MultiBidWarningCode
 	AdServerTargetingWarningCode
+	BidAdjustmentWarningCode
 )
 
 // Coder provides an error or warning code with severity.
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 7a2ffd54..af9ac883 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -197,6 +197,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderAdprime:           adprime.Builder,
 		openrtb_ext.BidderAdrino:            adrino.Builder,
 		openrtb_ext.BidderAdsinteractive:    adsinteractive.Builder,
+		openrtb_ext.BidderAdsyield:          limelightDigital.Builder,
 		openrtb_ext.BidderAdtarget:          adtarget.Builder,
 		openrtb_ext.BidderAdtrgtme:          adtrgtme.Builder,
 		openrtb_ext.BidderAdtelligent:       adtelligent.Builder,
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 8e6f133f..e355c18c 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"github.com/golang/glog"
+	"github.com/prebid/prebid-server/bidadjustment"
 	"github.com/prebid/prebid-server/config/util"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/exchange/entities"
@@ -23,6 +24,7 @@ import (
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/version"
 
+	"github.com/prebid/openrtb/v19/adcom1"
 	nativeRequests "github.com/prebid/openrtb/v19/native1/request"
 	nativeResponse "github.com/prebid/openrtb/v19/native1/response"
 	"github.com/prebid/openrtb/v19/openrtb2"
@@ -56,7 +58,7 @@ type AdaptedBidder interface {
 	//
 	// Any errors will be user-facing in the API.
 	// Error messages should help publishers understand what might account for "bad" bids.
-	requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error)
+	requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error)
 }
 
 // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
@@ -115,7 +117,7 @@ type bidderAdapterConfig struct {
 	EndpointCompression string
 }
 
-func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) {
+func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) {
 	reject := hookExecutor.ExecuteBidderRequestStage(bidderRequest.BidRequest, string(bidderRequest.BidderName))
 	if reject != nil {
 		return nil, []error{reject}
@@ -335,9 +337,13 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 						}
 
 						originalBidCpm := 0.0
+						currencyAfterAdjustments := ""
 						if bidResponse.Bids[i].Bid != nil {
 							originalBidCpm = bidResponse.Bids[i].Bid.Price
 							bidResponse.Bids[i].Bid.Price = bidResponse.Bids[i].Bid.Price * adjustmentFactor * conversionRate
+
+							bidType := getBidTypeForAdjustments(bidResponse.Bids[i].BidType, bidResponse.Bids[i].Bid.ImpID, bidderRequest.BidRequest.Imp)
+							bidResponse.Bids[i].Bid.Price, currencyAfterAdjustments = bidadjustment.Apply(ruleToAdjustments, bidResponse.Bids[i], bidderRequest.BidderName, seatBidMap[bidderRequest.BidderName].Currency, reqInfo, bidType)
 						}
 
 						if _, ok := seatBidMap[bidderName]; !ok {
@@ -360,6 +366,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 							OriginalBidCPM: originalBidCpm,
 							OriginalBidCur: bidResponse.Currency,
 						})
+						seatBidMap[bidderName].Currency = currencyAfterAdjustments
 					}
 				} else {
 					// If no conversions found, do not handle the bid
@@ -687,3 +694,18 @@ func compressToGZIP(requestBody []byte) []byte {
 	w.Close()
 	return b.Bytes()
 }
+
+func getBidTypeForAdjustments(bidType openrtb_ext.BidType, impID string, imp []openrtb2.Imp) string {
+	if bidType == openrtb_ext.BidTypeVideo {
+		for _, imp := range imp {
+			if imp.ID == impID {
+				if imp.Video.Plcmt == adcom1.VideoPlcmtAccompanyingContent {
+					return "video-outstream"
+				}
+				break
+			}
+		}
+		return "video-instream"
+	}
+	return string(bidType)
+}
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index 44d44a35..8283a674 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -31,8 +31,8 @@ type validatedBidder struct {
 	bidder AdaptedBidder
 }
 
-func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) {
-	seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor)
+func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) {
+	seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments)
 	for _, seatBid := range seatBids {
 		if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
 			errs = append(errs, validationErrors...)
diff --git a/exchange/events.go b/exchange/events.go
index 73ca6ec9..fd52d6b6 100644
--- a/exchange/events.go
+++ b/exchange/events.go
@@ -28,7 +28,7 @@ type eventTracking struct {
 func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Time, account *config.Account, bidderInfos config.BidderInfos, externalURL string) *eventTracking {
 	return &eventTracking{
 		accountID:          account.ID,
-		enabledForAccount:  account.EventsEnabled,
+		enabledForAccount:  account.Events.IsEnabled(),
 		enabledForRequest:  requestExtPrebid != nil && requestExtPrebid.Events != nil,
 		auctionTimestampMs: ts.UnixNano() / 1e+6,
 		integrationType:    getIntegrationType(requestExtPrebid),
@@ -60,7 +60,7 @@ func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName
 
 // isModifyingVASTXMLAllowed returns true if this bidder config allows modifying VAST XML for event tracking
 func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool {
-	return ev.bidderInfos[bidderName].ModifyingVastXmlAllowed && (ev.enabledForAccount || ev.enabledForRequest)
+	return ev.bidderInfos[bidderName].ModifyingVastXmlAllowed && ev.isEventAllowed()
 }
 
 // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string
@@ -81,7 +81,7 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *entities.PbsOrtbBid, bidderName o
 
 // modifyBidJSON injects "wurl" (win) event url if needed, otherwise returns original json
 func (ev *eventTracking) modifyBidJSON(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName, jsonBytes []byte) ([]byte, error) {
-	if !(ev.enabledForAccount || ev.enabledForRequest) || pbsBid.BidType == openrtb_ext.BidTypeVideo {
+	if !ev.isEventAllowed() || pbsBid.BidType == openrtb_ext.BidTypeVideo {
 		return jsonBytes, nil
 	}
 	var winEventURL string
@@ -104,7 +104,7 @@ func (ev *eventTracking) modifyBidJSON(pbsBid *entities.PbsOrtbBid, bidderName o
 
 // makeBidExtEvents make the data for bid.ext.prebid.events if needed, otherwise returns nil
 func (ev *eventTracking) makeBidExtEvents(pbsBid *entities.PbsOrtbBid, bidderName openrtb_ext.BidderName) *openrtb_ext.ExtBidPrebidEvents {
-	if !(ev.enabledForAccount || ev.enabledForRequest) || pbsBid.BidType == openrtb_ext.BidTypeVideo {
+	if !ev.isEventAllowed() || pbsBid.BidType == openrtb_ext.BidTypeVideo {
 		return nil
 	}
 	return &openrtb_ext.ExtBidPrebidEvents{
@@ -129,3 +129,8 @@ func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *entiti
 			Integration: ev.integrationType,
 		})
 }
+
+// isEnabled checks if events are enabled by default or on account/request level
+func (ev *eventTracking) isEventAllowed() bool {
+	return ev.enabledForAccount || ev.enabledForRequest
+}
diff --git a/exchange/exchange.go b/exchange/exchange.go
index c3728180..7ce070eb 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adservertargeting"
+	"github.com/prebid/prebid-server/bidadjustment"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/errortypes"
@@ -317,6 +318,15 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue)
 	errs = append(errs, floorErrs...)
 
+	mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments)
+	if err != nil {
+		if errortypes.ContainsFatalError([]error{err}) {
+			return nil, err
+		}
+		errs = append(errs, err)
+	}
+	bidAdjustmentRules := bidadjustment.BuildRules(mergedBidAdj)
+
 	e.me.RecordRequestPrivacy(privacyLabels)
 
 	if len(r.StoredAuctionResponses) > 0 || len(r.StoredBidResponses) > 0 {
@@ -355,8 +365,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 		} else if r.Account.AlternateBidderCodes != nil {
 			alternateBidderCodes = *r.Account.AlternateBidderCodes
 		}
-
-		adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime)
+		adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules)
 		r.MakeBidsTimeInfo = buildMakeBidsTimeInfoMap(adapterExtra)
 	}
 
@@ -629,7 +638,8 @@ func (e *exchange) getAllBids(
 	alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes,
 	experiment *openrtb_ext.Experiment,
 	hookExecutor hookexecution.StageExecutor,
-	pbsRequestStartTime time.Time) (
+	pbsRequestStartTime time.Time,
+	bidAdjustmentRules map[string][]openrtb_ext.Adjustment) (
 	map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra,
 	*openrtb_ext.Fledge,
@@ -670,7 +680,7 @@ func (e *exchange) getAllBids(
 				addCallSignHeader:   isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]),
 				bidAdjustments:      bidAdjustments,
 			}
-			seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor)
+			seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules)
 
 			// Add in time reporting
 			elapsed := time.Since(start)
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index a5a1ecff..1b959c5d 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -106,6 +106,7 @@ const (
 	BidderAdprime           BidderName = "adprime"
 	BidderAdrino            BidderName = "adrino"
 	BidderAdsinteractive    BidderName = "adsinteractive"
+	BidderAdsyield          BidderName = "adsyield"
 	BidderAdtarget          BidderName = "adtarget"
 	BidderAdtrgtme          BidderName = "adtrgtme"
 	BidderAdtelligent       BidderName = "adtelligent"
@@ -293,6 +294,7 @@ func CoreBidderNames() []BidderName {
 		BidderAdprime,
 		BidderAdrino,
 		BidderAdsinteractive,
+		BidderAdsyield,
 		BidderAdtarget,
 		BidderAdtrgtme,
 		BidderAdtelligent,
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 5e222ab7..a6c7d5dc 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -79,7 +79,8 @@ type ExtRequestPrebid struct {
 	// any other value or an empty string disables trace output at all.
 	Trace string `json:"trace,omitempty"`
 
-	AdServerTargeting []AdServerTarget `json:"adservertargeting,omitempty"`
+	BidAdjustments    *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"`
+	AdServerTargeting []AdServerTarget                `json:"adservertargeting,omitempty"`
 }
 
 type AdServerTarget struct {
@@ -152,6 +153,32 @@ type ExtRequestPrebidCacheVAST struct {
 	ReturnCreative *bool `json:"returnCreative,omitempty"`
 }
 
+// ExtRequestPrebidBidAdjustments defines the contract for bidrequest.ext.prebid.bidadjustments
+type ExtRequestPrebidBidAdjustments struct {
+	MediaType MediaType `json:"mediatype,omitempty"`
+}
+
+// AdjustmentsByDealID maps a dealID to a slice of bid adjustments
+type AdjustmentsByDealID map[string][]Adjustment
+
+// MediaType defines contract for bidrequest.ext.prebid.bidadjustments.mediatype
+// BidderName will map to a DealID that will map to a slice of bid adjustments
+type MediaType struct {
+	Banner         map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"`
+	VideoInstream  map[BidderName]AdjustmentsByDealID `json:"video-instream,omitempty"`
+	VideoOutstream map[BidderName]AdjustmentsByDealID `json:"video-outstream,omitempty"`
+	Audio          map[BidderName]AdjustmentsByDealID `json:"audio,omitempty"`
+	Native         map[BidderName]AdjustmentsByDealID `json:"native,omitempty"`
+	WildCard       map[BidderName]AdjustmentsByDealID `json:"*,omitempty"`
+}
+
+// Adjustment defines the object that will be present in the slice of bid adjustments found from MediaType map
+type Adjustment struct {
+	Type     string  `json:"adjtype,omitempty"`
+	Value    float64 `json:"value,omitempty"`
+	Currency string  `json:"currency,omitempty"`
+}
+
 // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting
 type ExtRequestTargeting struct {
 	PriceGranularity     *PriceGranularity        `json:"pricegranularity,omitempty"`
diff --git a/static/bidder-info/adsyield.yaml b/static/bidder-info/adsyield.yaml
new file mode 100644
index 00000000..dadd9cc3
--- /dev/null
+++ b/static/bidder-info/adsyield.yaml
@@ -0,0 +1,16 @@
+endpoint: "http://ads-pbs.open-adsyield.com/openrtb/{{.PublisherID}}?host={{.Host}}"
+maintainer:
+  email: "engineering@project-limelight.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - audio
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - audio
+      - native
diff --git a/static/bidder-params/adsyield.json b/static/bidder-params/adsyield.json
new file mode 100644
index 00000000..c7c53088
--- /dev/null
+++ b/static/bidder-params/adsyield.json
@@ -0,0 +1,23 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Adsyield Adapter Params",
+  "description": "A schema which validates params accepted by the Adsyield adapter",
+  "type": "object",
+
+  "properties": {
+    "host": {
+      "type": "string",
+      "description": "Ad network's RTB host",
+      "format": "hostname",
+      "pattern": "^.+\\..+$"
+    },
+    "publisherId": {
+      "type": ["integer", "string"],
+      "description": "Publisher ID",
+      "minimum": 1,
+      "pattern": "^[1-9][0-9]*$"
+    }
+  },
+
+  "required": ["host", "publisherId"]
+}


Test patch:
diff --git a/account/account_test.go b/account/account_test.go
index 95fa8ada..4bac0894 100644
--- a/account/account_test.go
+++ b/account/account_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/stored_requests"
+	"github.com/prebid/prebid-server/util/ptrutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -579,3 +580,66 @@ func TestAccountUpgradeStatusGetAccount(t *testing.T) {
 		})
 	}
 }
+
+func TestDeprecateEventsEnabledField(t *testing.T) {
+	testCases := []struct {
+		name    string
+		account *config.Account
+		want    *bool
+	}{
+		{
+			name:    "account is nil",
+			account: nil,
+			want:    nil,
+		},
+		{
+			name: "account.EventsEnabled is nil, account.Events.Enabled is nil",
+			account: &config.Account{
+				EventsEnabled: nil,
+				Events: config.Events{
+					Enabled: nil,
+				},
+			},
+			want: nil,
+		},
+		{
+			name: "account.EventsEnabled is nil, account.Events.Enabled is non-nil",
+			account: &config.Account{
+				EventsEnabled: nil,
+				Events: config.Events{
+					Enabled: ptrutil.ToPtr(true),
+				},
+			},
+			want: ptrutil.ToPtr(true),
+		},
+		{
+			name: "account.EventsEnabled is non-nil, account.Events.Enabled is nil",
+			account: &config.Account{
+				EventsEnabled: ptrutil.ToPtr(true),
+				Events: config.Events{
+					Enabled: nil,
+				},
+			},
+			want: ptrutil.ToPtr(true),
+		},
+		{
+			name: "account.EventsEnabled is non-nil, account.Events.Enabled is non-nil",
+			account: &config.Account{
+				EventsEnabled: ptrutil.ToPtr(false),
+				Events: config.Events{
+					Enabled: ptrutil.ToPtr(true),
+				},
+			},
+			want: ptrutil.ToPtr(true),
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			deprecateEventsEnabledField(test.account)
+			if test.account != nil {
+				assert.Equal(t, test.want, test.account.Events.Enabled)
+			}
+		})
+	}
+}
diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go
index 57914228..7553519f 100644
--- a/adapters/pubmatic/pubmatic_test.go
+++ b/adapters/pubmatic/pubmatic_test.go
@@ -122,7 +122,7 @@ func TestParseImpressionObject(t *testing.T) {
 			expectedBidfloor: 0.12,
 		},
 		{
-			name: "imp.bidfloor set and kadfloor set, preference to kadfloor",
+			name: "imp.bidfloor set and kadfloor set, higher imp.bidfloor",
 			args: args{
 				imp: &openrtb2.Imp{
 					BidFloor: 0.12,
@@ -130,7 +130,18 @@ func TestParseImpressionObject(t *testing.T) {
 					Ext:      json.RawMessage(`{"bidder":{"kadfloor":"0.11"}}`),
 				},
 			},
-			expectedBidfloor: 0.11,
+			expectedBidfloor: 0.12,
+		},
+		{
+			name: "imp.bidfloor set and kadfloor set, higher kadfloor",
+			args: args{
+				imp: &openrtb2.Imp{
+					BidFloor: 0.12,
+					Video:    &openrtb2.Video{},
+					Ext:      json.RawMessage(`{"bidder":{"kadfloor":"0.13"}}`),
+				},
+			},
+			expectedBidfloor: 0.13,
 		},
 		{
 			name: "kadfloor string set with whitespace",
diff --git a/bidadjustment/apply_test.go b/bidadjustment/apply_test.go
new file mode 100644
index 00000000..27daa9c7
--- /dev/null
+++ b/bidadjustment/apply_test.go
@@ -0,0 +1,532 @@
+package bidadjustment
+
+import (
+	"testing"
+
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestGetAndApply(t *testing.T) {
+	var (
+		adjCur string = "EUR"
+		bidCur string = "USA"
+	)
+
+	testCases := []struct {
+		name                   string
+		givenRuleToAdjustments map[string][]openrtb_ext.Adjustment
+		givenBidderName        openrtb_ext.BidderName
+		givenBidInfo           *adapters.TypedBid
+		givenBidType           string
+		setMock                func(m *mock.Mock)
+		expectedBidPrice       float64
+		expectedCurrency       string
+	}{
+		{
+			name: "CpmAdjustment",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  10.0,
+					DealID: "dealId",
+				},
+			},
+			givenBidType: string(openrtb_ext.BidTypeBanner),
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.0,
+						Currency: adjCur,
+					},
+				},
+				"banner|bidderA|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 2.0,
+					},
+				},
+			},
+			givenBidderName:  "bidderA",
+			setMock:          func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) },
+			expectedBidPrice: 7.5,
+			expectedCurrency: bidCur,
+		},
+		{
+			name: "StaticAdjustment",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  10.0,
+					DealID: "dealId",
+				},
+			},
+			givenBidType: string(openrtb_ext.BidTypeBanner),
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|bidderA|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.0,
+						Currency: adjCur,
+					},
+				},
+				"banner|bidderA|*": {
+					{
+						Type:     AdjustmentTypeStatic,
+						Value:    2.0,
+						Currency: adjCur,
+					},
+				},
+			},
+			givenBidderName:  "bidderA",
+			setMock:          nil,
+			expectedBidPrice: 2.0,
+			expectedCurrency: adjCur,
+		},
+		{
+			name: "MultiplierAdjustment",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  10.0,
+					DealID: "dealId",
+				},
+			},
+			givenBidType: VideoInstream,
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|*|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.0,
+						Currency: adjCur,
+					},
+				},
+				"video-instream|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 2.0,
+					},
+				},
+			},
+			givenBidderName:  "bidderA",
+			setMock:          nil,
+			expectedBidPrice: 20.0,
+			expectedCurrency: bidCur,
+		},
+		{
+			name: "CpmAndMultiplierAdjustments",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  10.0,
+					DealID: "dealId",
+				},
+			},
+			givenBidType: VideoInstream,
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"video-instream|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 2.0,
+					},
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.0,
+						Currency: adjCur,
+					},
+				},
+			},
+			givenBidderName:  "bidderA",
+			setMock:          func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) },
+			expectedBidPrice: 17.5,
+			expectedCurrency: bidCur,
+		},
+		{
+			name: "DealIdPresentAndNegativeAdjustedPrice",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  1.0,
+					DealID: "dealId",
+				},
+			},
+			givenBidType: VideoInstream,
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|*|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.0,
+						Currency: adjCur,
+					},
+				},
+			},
+			givenBidderName:  "bidderA",
+			setMock:          func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) },
+			expectedBidPrice: 0.0,
+			expectedCurrency: bidCur,
+		},
+		{
+			name: "NoDealIdNegativeAdjustedPrice",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  1.0,
+					DealID: "",
+				},
+			},
+			givenBidType: string(openrtb_ext.BidTypeAudio),
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|*|*": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.0,
+						Currency: adjCur,
+					},
+				},
+			},
+			givenBidderName:  "bidderA",
+			setMock:          func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) },
+			expectedBidPrice: 0.1,
+			expectedCurrency: bidCur,
+		},
+		{
+			name: "NilMap",
+			givenBidInfo: &adapters.TypedBid{
+				Bid: &openrtb2.Bid{
+					Price:  10.0,
+					DealID: "dealId",
+				},
+			},
+			givenBidType:           string(openrtb_ext.BidTypeBanner),
+			givenRuleToAdjustments: nil,
+			givenBidderName:        "bidderA",
+			setMock:                nil,
+			expectedBidPrice:       10.0,
+			expectedCurrency:       bidCur,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			reqInfo := adapters.ExtraRequestInfo{}
+			if test.setMock != nil {
+				mockConversions := &mockConversions{}
+				test.setMock(&mockConversions.Mock)
+				reqInfo = adapters.NewExtraRequestInfo(mockConversions)
+			}
+			bidPrice, currencyAfterAdjustment := Apply(test.givenRuleToAdjustments, test.givenBidInfo, test.givenBidderName, bidCur, &reqInfo, test.givenBidType)
+			assert.Equal(t, test.expectedBidPrice, bidPrice)
+			assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment)
+		})
+	}
+}
+
+type mockConversions struct {
+	mock.Mock
+}
+
+func (m mockConversions) GetRate(from string, to string) (float64, error) {
+	args := m.Called(from, to)
+	return args.Get(0).(float64), args.Error(1)
+}
+
+func (m mockConversions) GetRates() *map[string]map[string]float64 {
+	args := m.Called()
+	return args.Get(0).(*map[string]map[string]float64)
+}
+
+func TestApply(t *testing.T) {
+	var (
+		adjCur string = "EUR"
+		bidCur string = "USA"
+	)
+
+	testCases := []struct {
+		name             string
+		givenAdjustments []openrtb_ext.Adjustment
+		setMock          func(m *mock.Mock)
+		givenBidPrice    float64
+		expectedBidPrice float64
+		expectedCurrency string
+	}{
+		{
+			name:             "CpmAdjustment",
+			givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 1.0, Currency: adjCur}},
+			givenBidPrice:    10.58687,
+			setMock:          func(m *mock.Mock) { m.On("GetRate", adjCur, bidCur).Return(2.5, nil) },
+			expectedBidPrice: 8.0869,
+			expectedCurrency: bidCur,
+		},
+		{
+			name:             "StaticAdjustment",
+			givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 4.0, Currency: adjCur}},
+			givenBidPrice:    10.0,
+			setMock:          nil,
+			expectedBidPrice: 4.0,
+			expectedCurrency: adjCur,
+		},
+		{
+			name:             "MultiplierAdjustment",
+			givenAdjustments: []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 3.0}},
+			givenBidPrice:    10.0,
+			setMock:          nil,
+			expectedBidPrice: 30.0,
+			expectedCurrency: bidCur,
+		},
+		{
+			name:             "NilAdjustment",
+			givenAdjustments: nil,
+			givenBidPrice:    10.0,
+			setMock:          nil,
+			expectedBidPrice: 10.0,
+			expectedCurrency: bidCur,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			reqInfo := adapters.ExtraRequestInfo{}
+			if test.setMock != nil {
+				mockConversions := &mockConversions{}
+				test.setMock(&mockConversions.Mock)
+				reqInfo = adapters.NewExtraRequestInfo(mockConversions)
+			}
+
+			bidPrice, currencyAfterAdjustment := apply(test.givenAdjustments, test.givenBidPrice, bidCur, &reqInfo)
+			assert.Equal(t, test.expectedBidPrice, bidPrice)
+			assert.Equal(t, test.expectedCurrency, currencyAfterAdjustment)
+		})
+	}
+}
+
+func TestGet(t *testing.T) {
+	testCases := []struct {
+		name                   string
+		givenRuleToAdjustments map[string][]openrtb_ext.Adjustment
+		givenBidType           openrtb_ext.BidType
+		givenBidderName        openrtb_ext.BidderName
+		givenDealId            string
+		expected               []openrtb_ext.Adjustment
+	}{
+		{
+			name: "Priority1",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+				"banner|bidderA|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 2.0,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+		},
+		{
+			name: "Priority2",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|*": {
+					{
+						Type:  AdjustmentTypeStatic,
+						Value: 5.0,
+					},
+				},
+				"banner|*|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 2.0,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0}},
+		},
+		{
+			name: "Priority3",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|*|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    3.0,
+						Currency: "USD",
+					},
+				},
+				"*|bidderA|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}},
+		},
+		{
+			name: "Priority4",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|bidderA|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    3.0,
+						Currency: "USD",
+					},
+				},
+				"banner|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}},
+		},
+		{
+			name: "Priority5",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|*|*": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    3.0,
+						Currency: "USD",
+					},
+				},
+				"*|bidderA|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}},
+		},
+		{
+			name: "Priority6",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|bidderA|*": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    3.0,
+						Currency: "USD",
+					},
+				},
+				"*|*|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}},
+		},
+		{
+			name: "Priority7",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|*|dealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    3.0,
+						Currency: "USD",
+					},
+				},
+				"*|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"}},
+		},
+		{
+			name: "Priority8",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"*|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+				"banner|bidderA|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderB",
+			givenDealId:     "dealId",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+		},
+		{
+			name: "NoDealID",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+				"banner|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeBanner,
+			givenBidderName: "bidderA",
+			givenDealId:     "",
+			expected:        []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+		},
+		{
+			name: "NoPriorityRulesMatch",
+			givenRuleToAdjustments: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+				"banner|bidderA|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 2.0,
+					},
+				},
+			},
+			givenBidType:    openrtb_ext.BidTypeVideo,
+			givenBidderName: "bidderB",
+			givenDealId:     "diffDealId",
+			expected:        nil,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			adjArray := get(test.givenRuleToAdjustments, string(test.givenBidType), string(test.givenBidderName), test.givenDealId)
+			assert.Equal(t, test.expected, adjArray)
+		})
+	}
+}
diff --git a/bidadjustment/build_rules_test.go b/bidadjustment/build_rules_test.go
new file mode 100644
index 00000000..263a7821
--- /dev/null
+++ b/bidadjustment/build_rules_test.go
@@ -0,0 +1,458 @@
+package bidadjustment
+
+import (
+	"testing"
+
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBuildRules(t *testing.T) {
+	testCases := []struct {
+		name                string
+		givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments
+		expectedRules       map[string][]openrtb_ext.Adjustment
+	}{
+		{
+			name: "OneAdjustment",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+				},
+			},
+			expectedRules: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+			},
+		},
+		{
+			name: "MultipleAdjustments",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+						"*": {
+							"diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 1.1, Currency: "USD"}},
+							"*":          []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"}},
+						},
+					},
+					VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"*": {
+							"*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}, {Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}},
+						},
+					},
+					VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderB": {
+							"*": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 0.25, Currency: "USD"}},
+						},
+					},
+				},
+			},
+			expectedRules: map[string][]openrtb_ext.Adjustment{
+				"banner|bidderA|dealId": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+				},
+				"banner|*|diffDealId": {
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    1.1,
+						Currency: "USD",
+					},
+				},
+				"banner|*|*": {
+					{
+						Type:     AdjustmentTypeStatic,
+						Value:    5.0,
+						Currency: "USD",
+					},
+				},
+				"video-instream|*|*": {
+					{
+						Type:  AdjustmentTypeMultiplier,
+						Value: 1.1,
+					},
+					{
+						Type:     AdjustmentTypeCPM,
+						Value:    0.18,
+						Currency: "USD",
+					},
+				},
+				"video-outstream|bidderB|*": {
+					{
+						Type:     AdjustmentTypeStatic,
+						Value:    0.25,
+						Currency: "USD",
+					},
+				},
+			},
+		},
+		{
+			name:                "NilAdjustments",
+			givenBidAdjustments: nil,
+			expectedRules:       nil,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			rules := BuildRules(test.givenBidAdjustments)
+			assert.Equal(t, test.expectedRules, rules)
+		})
+	}
+}
+
+func TestMergeAndValidate(t *testing.T) {
+	testCases := []struct {
+		name                   string
+		givenRequestWrapper    *openrtb_ext.RequestWrapper
+		givenAccount           *config.Account
+		expectError            bool
+		expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments
+	}{
+		{
+			name: "ValidReqAndAcctAdjustments",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectError: false,
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.1}},
+						},
+						"bidderB": {
+							"dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "InvalidReqAdjustment",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 200}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectError:            true,
+			expectedBidAdjustments: nil,
+		},
+		{
+			name: "InvalidAcctAdjustment",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: "multiplier", Value: -1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectError:            true,
+			expectedBidAdjustments: nil,
+		},
+		{
+			name: "InvalidJSON",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{}}`)},
+			},
+			givenAccount:           &config.Account{},
+			expectError:            true,
+			expectedBidAdjustments: nil,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			mergedBidAdj, err := Merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments)
+			if !test.expectError {
+				assert.NoError(t, err)
+			} else {
+				assert.Error(t, err)
+			}
+			assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj)
+		})
+	}
+}
+
+func TestMerge(t *testing.T) {
+	testCases := []struct {
+		name                   string
+		givenRequestWrapper    *openrtb_ext.RequestWrapper
+		givenAccount           *config.Account
+		expectedBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments
+	}{
+		{
+			name: "DiffBidderNames",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+						"bidderB": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "RequestTakesPrecedence",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"audio":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderA": {
+								"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "DiffDealIds",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-instream":{"bidderA":{"dealId":[{ "adjtype": "static", "value": 3.00, "currency": "USD"}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderA": {
+								"diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId":     []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 3.00, Currency: "USD"}},
+							"diffDealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "DiffBidderNamesCpm",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"native":{"bidderA":{"dealId":[{"adjtype": "cpm", "value": 0.18, "currency": "USD"}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 0.18, Currency: "USD"}},
+						},
+						"bidderB": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "ReqAdjVideoAcctAdjBanner",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-outstream":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderB": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+						},
+					},
+					VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "RequestNilPrebid",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderB": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "AcctWildCardRequestVideo",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"video-instream":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{
+				BidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+					MediaType: openrtb_ext.MediaType{
+						WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+							"bidderB": {
+								"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+							},
+						},
+					},
+				},
+			},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderB": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.5}},
+						},
+					},
+					VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "NilReqExtPrebidAndAcctBidAdj",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"ext":{"bidder": {}}}`)},
+			},
+			givenAccount:           &config.Account{},
+			expectedBidAdjustments: nil,
+		},
+		{
+			name: "NilAcctBidAdj",
+			givenRequestWrapper: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"bidadjustments":{"mediatype":{"banner":{"bidderA":{"dealId":[{ "adjtype": "multiplier", "value": 1.1}]}}}}}}`)},
+			},
+			givenAccount: &config.Account{},
+			expectedBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			mergedBidAdj, err := merge(test.givenRequestWrapper, test.givenAccount.BidAdjustments)
+			assert.NoError(t, err)
+			assert.Equal(t, test.expectedBidAdjustments, mergedBidAdj)
+		})
+	}
+}
diff --git a/bidadjustment/validate_test.go b/bidadjustment/validate_test.go
new file mode 100644
index 00000000..a0b4eb43
--- /dev/null
+++ b/bidadjustment/validate_test.go
@@ -0,0 +1,338 @@
+package bidadjustment
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestValidate(t *testing.T) {
+	testCases := []struct {
+		name                string
+		givenBidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments
+		expected            bool
+	}{
+		{
+			name: "OneAdjustmentValid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "MultipleAdjustmentsValid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+					VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"}},
+						},
+					},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "MixOfValidandInvalid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+						},
+					},
+					VideoOutstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: 3.0, Currency: ""}},
+						},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "WildCardInvalid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					WildCard: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: -1.1, Currency: "USD"}},
+						},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "AudioInvalid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Audio: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeStatic, Value: 5.0, Currency: ""}},
+						},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "NativeInvalid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Native: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeCPM, Value: -1.1, Currency: "USD"}},
+						},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "BannerInvalid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					Banner: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 150}},
+						},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "InstreamInvalid",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{
+				MediaType: openrtb_ext.MediaType{
+					VideoInstream: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+						"bidderA": {
+							"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 150}},
+						},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name:                "EmptyBidAdjustments",
+			givenBidAdjustments: &openrtb_ext.ExtRequestPrebidBidAdjustments{},
+			expected:            true,
+		},
+		{
+			name:                "NilBidAdjustments",
+			givenBidAdjustments: nil,
+			expected:            true,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			actual := Validate(test.givenBidAdjustments)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestValidateForMediaType(t *testing.T) {
+	testCases := []struct {
+		name        string
+		givenBidAdj map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID
+		expected    bool
+	}{
+		{
+			name: "OneAdjustmentValid",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: 1.1}},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "OneAdjustmentInvalid",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": []openrtb_ext.Adjustment{{Type: AdjustmentTypeMultiplier, Value: -1.1}},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "MultipleAdjustmentsValid",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": []openrtb_ext.Adjustment{
+						{Type: AdjustmentTypeMultiplier, Value: 1.1},
+						{Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"},
+					},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "MultipleAdjustmentsInvalid",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": []openrtb_ext.Adjustment{
+						{Type: AdjustmentTypeMultiplier, Value: -1.1},
+						{Type: AdjustmentTypeCPM, Value: -3.0, Currency: "USD"},
+					},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "MultipleDealIdsValid",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": []openrtb_ext.Adjustment{
+						{Type: AdjustmentTypeStatic, Value: 3.0, Currency: "USD"},
+					},
+					"diffDealId": []openrtb_ext.Adjustment{
+						{Type: AdjustmentTypeMultiplier, Value: 1.1},
+					},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "MultipleBiddersValid",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": []openrtb_ext.Adjustment{
+						{Type: AdjustmentTypeStatic, Value: 5.0, Currency: "USD"},
+					},
+				},
+				"bidderB": {
+					"dealId": []openrtb_ext.Adjustment{
+						{Type: AdjustmentTypeCPM, Value: 3.0, Currency: "USD"},
+					},
+				},
+			},
+			expected: true,
+		},
+		{
+			name:        "NilBidAdj",
+			givenBidAdj: nil,
+			expected:    true,
+		},
+		{
+			name: "NilBidderToAdjustmentsByDealID",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": nil,
+			},
+			expected: false,
+		},
+		{
+			name: "NilDealIdToAdjustments",
+			givenBidAdj: map[openrtb_ext.BidderName]openrtb_ext.AdjustmentsByDealID{
+				"bidderA": {
+					"dealId": nil,
+				},
+			},
+			expected: false,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			actual := validateForMediaType(test.givenBidAdj)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func TestValidateAdjustment(t *testing.T) {
+	testCases := []struct {
+		name            string
+		givenAdjustment openrtb_ext.Adjustment
+		expected        bool
+	}{
+		{
+			name: "ValidCpm",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:     AdjustmentTypeCPM,
+				Value:    5.0,
+				Currency: "USD",
+			},
+			expected: true,
+		},
+		{
+			name: "ValidMultiplier",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:  AdjustmentTypeMultiplier,
+				Value: 2.0,
+			},
+			expected: true,
+		},
+		{
+			name: "ValidStatic",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:     AdjustmentTypeStatic,
+				Value:    3.0,
+				Currency: "USD",
+			},
+			expected: true,
+		},
+		{
+			name: "InvalidCpm",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:     AdjustmentTypeCPM,
+				Value:    5.0,
+				Currency: "",
+			},
+			expected: false,
+		},
+		{
+			name: "InvalidMultiplier",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:  AdjustmentTypeMultiplier,
+				Value: 200,
+			},
+			expected: false,
+		},
+		{
+			name: "InvalidStatic",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:     AdjustmentTypeStatic,
+				Value:    -3.0,
+				Currency: "USD",
+			},
+			expected: false,
+		},
+		{
+			name: "InvalidAdjType",
+			givenAdjustment: openrtb_ext.Adjustment{
+				Type:  "Invalid",
+				Value: 1.0,
+			},
+			expected: false,
+		},
+		{
+			name:            "EmptyAdjustment",
+			givenAdjustment: openrtb_ext.Adjustment{},
+			expected:        false,
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			actual := validateAdjustment(test.givenAdjustment)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
diff --git a/config/config_test.go b/config/config_test.go
index f45d7fcd..fbd96d3f 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/util/ptrutil"
 	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 )
@@ -185,6 +186,8 @@ func TestDefaults(t *testing.T) {
 	cmpBools(t, "account_defaults.price_floors.use_dynamic_data", false, cfg.AccountDefaults.PriceFloors.UseDynamicData)
 	cmpInts(t, "account_defaults.price_floors.max_rules", 100, cfg.AccountDefaults.PriceFloors.MaxRule)
 	cmpInts(t, "account_defaults.price_floors.max_schema_dims", 3, cfg.AccountDefaults.PriceFloors.MaxSchemaDims)
+	cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, false)
+	cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled)
 
 	cmpBools(t, "hooks.enabled", false, cfg.Hooks.Enabled)
 	cmpStrings(t, "validations.banner_creative_max_size", "skip", cfg.Validations.BannerCreativeMaxSize)
@@ -311,6 +314,7 @@ func TestDefaults(t *testing.T) {
 	assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2")
 }
 
+// When adding a new field, make sure the indentations are spaces not tabs otherwise read config may fail to parse the new field value.
 var fullConfig = []byte(`
 gdpr:
   host_vendor_id: 15
@@ -446,6 +450,9 @@ hooks:
 price_floors:
     enabled: true
 account_defaults:
+    events_enabled: false
+    events:
+        enabled: true
     price_floors:
         enabled: true
         enforce_floors_rate: 50
@@ -543,6 +550,8 @@ func TestFullConfig(t *testing.T) {
 	cmpBools(t, "account_defaults.price_floors.use_dynamic_data", true, cfg.AccountDefaults.PriceFloors.UseDynamicData)
 	cmpInts(t, "account_defaults.price_floors.max_rules", 120, cfg.AccountDefaults.PriceFloors.MaxRule)
 	cmpInts(t, "account_defaults.price_floors.max_schema_dims", 5, cfg.AccountDefaults.PriceFloors.MaxSchemaDims)
+	cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, true)
+	cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled)
 
 	//Assert the NonStandardPublishers was correctly unmarshalled
 	assert.Equal(t, []string{"pub1", "pub2"}, cfg.GDPR.NonStandardPublishers, "gdpr.non_standard_publishers")
@@ -3191,3 +3200,52 @@ func TestTCF2FeatureOneVendorException(t *testing.T) {
 		assert.Equal(t, tt.wantIsVendorException, value, tt.description)
 	}
 }
+
+func TestMigrateConfigEventsEnabled(t *testing.T) {
+	testCases := []struct {
+		name                  string
+		oldFieldValue         *bool
+		newFieldValue         *bool
+		expectedOldFieldValue *bool
+		expectedNewFieldValue *bool
+	}{
+		{
+			name:                  "Both old and new fields are nil",
+			oldFieldValue:         nil,
+			newFieldValue:         nil,
+			expectedOldFieldValue: nil,
+			expectedNewFieldValue: nil,
+		},
+		{
+			name:                  "Only old field is set",
+			oldFieldValue:         ptrutil.ToPtr(true),
+			newFieldValue:         nil,
+			expectedOldFieldValue: ptrutil.ToPtr(true),
+			expectedNewFieldValue: nil,
+		},
+		{
+			name:                  "Only new field is set",
+			oldFieldValue:         nil,
+			newFieldValue:         ptrutil.ToPtr(true),
+			expectedOldFieldValue: ptrutil.ToPtr(true),
+			expectedNewFieldValue: nil,
+		},
+		{
+			name:                  "Both old and new fields are set, override old field with new field value",
+			oldFieldValue:         ptrutil.ToPtr(false),
+			newFieldValue:         ptrutil.ToPtr(true),
+			expectedOldFieldValue: ptrutil.ToPtr(true),
+			expectedNewFieldValue: nil,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			updatedOldFieldValue, updatedNewFieldValue := migrateConfigEventsEnabled(tc.oldFieldValue, tc.newFieldValue)
+
+			assert.Equal(t, tc.expectedOldFieldValue, updatedOldFieldValue)
+			assert.Nil(t, updatedNewFieldValue)
+			assert.Nil(t, tc.expectedNewFieldValue)
+		})
+	}
+}
diff --git a/config/events_test.go b/config/events_test.go
index b4c196e9..4baa9066 100644
--- a/config/events_test.go
+++ b/config/events_test.go
@@ -3,6 +3,7 @@ package config
 import (
 	"testing"
 
+	"github.com/prebid/prebid-server/util/ptrutil"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -261,22 +262,29 @@ func TestValidate(t *testing.T) {
 		{
 			description: "Empty default URL",
 			events: Events{
-				Enabled: true,
+				Enabled: ptrutil.ToPtr(true),
 			},
 			expectErr: true,
 		},
 		{
 			description: "Events are disabled. Skips validations",
 			events: Events{
-				Enabled:    false,
+				Enabled:    ptrutil.ToPtr(false),
 				DefaultURL: "",
 			},
 			expectErr: false,
 		},
+		{
+			description: "Events are nil. Skip validations",
+			events: Events{
+				Enabled: nil,
+			},
+			expectErr: false,
+		},
 		{
 			description: "No VAST Events and default URL present",
 			events: Events{
-				Enabled:    true,
+				Enabled:    ptrutil.ToPtr(true),
 				DefaultURL: "http://prebid.org",
 			},
 			expectErr: false,
@@ -284,7 +292,7 @@ func TestValidate(t *testing.T) {
 		{
 			description: "Invalid VAST Event",
 			events: Events{
-				Enabled:    true,
+				Enabled:    ptrutil.ToPtr(true),
 				DefaultURL: "http://prebid.org",
 				VASTEvents: []VASTEvent{
 					{},
@@ -327,3 +335,34 @@ func TestValidateVASTEvents(t *testing.T) {
 		assert.Equal(t, !test.expectErr, err == nil, test.description)
 	}
 }
+
+func TestIsEnabled(t *testing.T) {
+	testCases := []struct {
+		name     string
+		events   Events
+		expected bool
+	}{
+		{
+			name:     "nil pointer",
+			events:   Events{},
+			expected: false,
+		},
+		{
+			name:     "event false",
+			events:   Events{Enabled: ptrutil.ToPtr(false)},
+			expected: false,
+		},
+		{
+			name:     "event true",
+			events:   Events{Enabled: ptrutil.ToPtr(true)},
+			expected: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			actual := tc.events.IsEnabled()
+			assert.Equal(t, tc.expected, actual)
+		})
+	}
+}
diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go
index d4bbc991..95711ba1 100644
--- a/endpoints/events/event_test.go
+++ b/endpoints/events/event_test.go
@@ -73,9 +73,9 @@ func (e *eventsMockAnalyticsModule) LogNotificationEventObject(ne *analytics.Not
 
 // Mock Account fetcher
 var mockAccountData = map[string]json.RawMessage{
-	"events_enabled":  json.RawMessage(`{"events_enabled":true}`),
-	"events_disabled": json.RawMessage(`{"events_enabled":false}`),
-	"malformed_acct":  json.RawMessage(`{"events_enabled":"invalid type"}`),
+	"events_enabled":  json.RawMessage(`{"events": {"enabled":true}}`),
+	"events_disabled": json.RawMessage(`{"events": {"enabled":false}}`),
+	"malformed_acct":  json.RawMessage(`{"events": {"enabled":"invalid type"}}`),
 }
 
 type mockAccountsFetcher struct {
@@ -359,7 +359,7 @@ func TestShouldReturnBadRequestWhenAnalyticsValueIsInvalid(t *testing.T) {
 	assert.Equal(t, "invalid request: unknown analytics: '4'\n", string(d))
 }
 
-func TestShouldNotPassEventToAnalyticsReporterWhenAccountNotFound(t *testing.T) {
+func TestShouldNotPassEventToAnalyticsReporterWhenAccountNotFoundAndDefaultIsFalse(t *testing.T) {
 
 	// mock AccountsFetcher
 	mockAccountsFetcher := &mockAccountsFetcher{
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index f291d2e8..053bcc10 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -114,6 +114,10 @@ func TestJsonSampleRequests(t *testing.T) {
 			"Assert request with ad server targeting is processing correctly",
 			"adservertargeting",
 		},
+		{
+			"Assert request with bid adjustments defined is processing correctly",
+			"bidadjustments",
+		},
 	}
 
 	for _, tc := range testSuites {
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index 0ad47982..6811249b 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -936,6 +936,7 @@ type mockBidderHandler struct {
 	BidderName string  `json:"bidderName"`
 	Currency   string  `json:"currency"`
 	Price      float64 `json:"price"`
+	DealID     string  `json:"dealid"`
 }
 
 func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) {
@@ -970,9 +971,10 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) {
 			{
 				Bid: []openrtb2.Bid{
 					{
-						ID:    b.BidderName + "-bid",
-						ImpID: openrtb2Request.Imp[0].ID,
-						Price: b.Price,
+						ID:     b.BidderName + "-bid",
+						ImpID:  openrtb2Request.Imp[0].ID,
+						Price:  b.Price,
+						DealID: b.DealID,
 					},
 				},
 				Seat: b.BidderName,
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 4e367ebe..3bd023dd 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -17,6 +17,7 @@ import (
 	"time"
 
 	"github.com/golang/glog"
+	"github.com/prebid/openrtb/v19/adcom1"
 	nativeRequests "github.com/prebid/openrtb/v19/native1/request"
 	nativeResponse "github.com/prebid/openrtb/v19/native1/response"
 	"github.com/prebid/openrtb/v19/openrtb2"
@@ -109,7 +110,7 @@ func TestSingleBidder(t *testing.T) {
 			bidAdjustments:      bidAdjustments,
 		}
 		extraInfo := &adapters.ExtraRequestInfo{}
-		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 		assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
 		assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
 
@@ -235,7 +236,7 @@ func TestSingleBidderGzip(t *testing.T) {
 			bidAdjustments:      bidAdjustments,
 		}
 		extraInfo := &adapters.ExtraRequestInfo{}
-		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 		assert.NotEmpty(t, extraInfo.MakeBidsTimeInfo.Durations)
 		assert.False(t, extraInfo.MakeBidsTimeInfo.AfterMakeBidsStartTime.IsZero())
 		assert.Len(t, seatBids, 1)
@@ -338,7 +339,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 		bidAdjustments:      bidAdjustments,
 	}
 	extraInfo := &adapters.ExtraRequestInfo{}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
 		{
 			Uri:            server.URL,
@@ -392,7 +393,7 @@ func TestSetGPCHeader(t *testing.T) {
 		bidAdjustments:      bidAdjustments,
 	}
 	extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -445,7 +446,7 @@ func TestSetGPCHeaderNil(t *testing.T) {
 		bidAdjustments:      bidAdjustments,
 	}
 	extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -517,7 +518,7 @@ func TestMultiBidder(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 
 	if len(seatBids) != 1 {
 		t.Fatalf("SeatBid should exist, because bids exist.")
@@ -898,6 +899,7 @@ func TestMultiCurrencies(t *testing.T) {
 			},
 			openrtb_ext.ExtAlternateBidderCodes{},
 			&hookexecution.EmptyHookExecutor{},
+			nil,
 		)
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
@@ -1057,6 +1059,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 			},
 			openrtb_ext.ExtAlternateBidderCodes{},
 			&hookexecution.EmptyHookExecutor{},
+			nil,
 		)
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
@@ -1235,6 +1238,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 			},
 			openrtb_ext.ExtAlternateBidderCodes{},
 			&hookexecution.EmptyHookExecutor{},
+			nil,
 		)
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
@@ -1552,6 +1556,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			openrtb_ext.ExtAlternateBidderCodes{},
 			&hookexecution.EmptyHookExecutor{},
+			nil,
 		)
 		assert.Len(t, seatBids, 1)
 
@@ -1671,6 +1676,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 			},
 			openrtb_ext.ExtAlternateBidderCodes{},
 			&hookexecution.EmptyHookExecutor{},
+			nil,
 		)
 		assert.Len(t, seatBids, 1)
 
@@ -1785,6 +1791,7 @@ func TestFledge(t *testing.T) {
 			},
 			openrtb_ext.ExtAlternateBidderCodes{},
 			&hookexecution.EmptyHookExecutor{},
+			nil,
 		)
 		assert.Len(t, seatBids, 1)
 		assert.NotNil(t, seatBids[0].FledgeAuctionConfigs)
@@ -1808,7 +1815,7 @@ func TestErrorReporting(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 	if bids != nil {
 		t.Errorf("There should be no seatbid if no http requests are returned.")
 	}
@@ -2042,7 +2049,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	_, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{PbsEntryPoint: metrics.ReqTypeORTB2Web}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	_, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{PbsEntryPoint: metrics.ReqTypeORTB2Web}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
@@ -2289,7 +2296,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) {
 		addCallSignHeader:   true,
 		bidAdjustments:      bidAdjustments,
 	}
-	_, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	_, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 
 	assert.Empty(t, errs, "no errors should be returned")
 }
@@ -2513,7 +2520,8 @@ func TestExtraBid(t *testing.T) {
 				},
 			},
 		},
-		&hookexecution.EmptyHookExecutor{})
+		&hookexecution.EmptyHookExecutor{},
+		nil)
 	assert.Nil(t, errs)
 	assert.Len(t, seatBids, 2)
 	sort.Slice(seatBids, func(i, j int) bool {
@@ -2626,7 +2634,8 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
 				},
 			},
 		},
-		&hookexecution.EmptyHookExecutor{})
+		&hookexecution.EmptyHookExecutor{},
+		nil)
 	assert.Equal(t, wantErrs, errs)
 	assert.Len(t, seatBids, 2)
 	assert.ElementsMatch(t, wantSeatBids, seatBids)
@@ -2736,7 +2745,8 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 				},
 			},
 		},
-		&hookexecution.EmptyHookExecutor{})
+		&hookexecution.EmptyHookExecutor{},
+		nil)
 	assert.Nil(t, errs)
 	assert.Len(t, seatBids, 2)
 	sort.Slice(seatBids, func(i, j int) bool {
@@ -2848,7 +2858,8 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
 				},
 			},
 		},
-		&hookexecution.EmptyHookExecutor{})
+		&hookexecution.EmptyHookExecutor{},
+		nil)
 	assert.Nil(t, errs)
 	assert.Len(t, seatBids, 2)
 	sort.Slice(seatBids, func(i, j int) bool {
@@ -2972,7 +2983,8 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) {
 				},
 			},
 		},
-		&hookexecution.EmptyHookExecutor{})
+		&hookexecution.EmptyHookExecutor{},
+		nil)
 	assert.Nil(t, errs)
 	assert.Len(t, seatBids, 2)
 	sort.Slice(seatBids, func(i, j int) bool {
@@ -2980,3 +2992,56 @@ func TestExtraBidWithMultiCurrencies(t *testing.T) {
 	})
 	assert.Equal(t, wantSeatBids, seatBids)
 }
+
+func TestGetBidType(t *testing.T) {
+	testCases := []struct {
+		name         string
+		givenBidType openrtb_ext.BidType
+		givenImpId   string
+		givenImp     []openrtb2.Imp
+		expected     string
+	}{
+		{
+			name: "VideoInstream",
+			givenImp: []openrtb2.Imp{
+				{
+					ID: "imp-id",
+					Video: &openrtb2.Video{
+						Plcmt: adcom1.VideoPlcmtInstream,
+					},
+				},
+			},
+			givenBidType: openrtb_ext.BidTypeVideo,
+			givenImpId:   "imp-id",
+			expected:     "video-instream",
+		},
+		{
+			name: "VideoOutstream",
+			givenImp: []openrtb2.Imp{
+				{
+					ID: "imp-id",
+					Video: &openrtb2.Video{
+						Plcmt: adcom1.VideoPlcmtAccompanyingContent,
+					},
+				},
+			},
+			givenBidType: openrtb_ext.BidTypeVideo,
+			givenImpId:   "imp-id",
+			expected:     "video-outstream",
+		},
+		{
+			name:         "NonVideoBidType",
+			givenImp:     []openrtb2.Imp{},
+			givenBidType: openrtb_ext.BidTypeBanner,
+			givenImpId:   "imp-id",
+			expected:     string(openrtb_ext.BidTypeBanner),
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			actual := getBidTypeForAdjustments(test.givenBidType, test.givenImpId, test.givenImp)
+			assert.Equal(t, test.expected, actual, "Bid type doesn't match")
+		})
+	}
+}
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index f863e932..e7340e7d 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -65,7 +65,7 @@ func TestAllValidBids(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 	assert.Len(t, seatBids, 1)
 	assert.Len(t, seatBids[0].Bids, 4)
 	assert.Len(t, errs, 0)
@@ -135,7 +135,7 @@ func TestAllBadBids(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 	assert.Len(t, seatBids, 1)
 	assert.Len(t, seatBids[0].Bids, 0)
 	assert.Len(t, errs, 7)
@@ -216,7 +216,7 @@ func TestMixedBids(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 	assert.Len(t, seatBids, 1)
 	assert.Len(t, seatBids[0].Bids, 3)
 	assert.Len(t, errs, 5)
@@ -345,7 +345,7 @@ func TestCurrencyBids(t *testing.T) {
 			addCallSignHeader:   false,
 			bidAdjustments:      bidAdjustments,
 		}
-		seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{})
+		seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
 		assert.Len(t, seatBids, 1)
 		assert.Len(t, seatBids[0].Bids, expectedValidBids)
 		assert.Len(t, errs, expectedErrs)
@@ -357,6 +357,6 @@ type mockAdaptedBidder struct {
 	errorResponse []error
 }
 
-func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) ([]*entities.PbsOrtbSeatBid, []error) {
+func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, []error) {
 	return b.bidResponse, b.errorResponse
 }
diff --git a/exchange/events_test.go b/exchange/events_test.go
index a808173e..24dedf1a 100644
--- a/exchange/events_test.go
+++ b/exchange/events_test.go
@@ -121,7 +121,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) {
 		},
 		{
 			name:      "banner: broken json expected to fail patching",
-			args:      args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
+			args:      args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			jsonBytes: []byte(`broken json`),
 			want:      nil,
 		},
@@ -153,3 +153,42 @@ func Test_eventsData_modifyBidJSON(t *testing.T) {
 		})
 	}
 }
+
+func Test_isEventAllowed(t *testing.T) {
+	type args struct {
+		enabledForAccount bool
+		enabledForRequest bool
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{
+			name: "enabled for account",
+			args: args{enabledForAccount: true, enabledForRequest: false},
+			want: true,
+		},
+		{
+			name: "enabled for request",
+			args: args{enabledForAccount: false, enabledForRequest: true},
+			want: true,
+		},
+		{
+			name: "disabled for account and request",
+			args: args{enabledForAccount: false, enabledForRequest: false},
+			want: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			evData := &eventTracking{
+				enabledForAccount: tt.args.enabledForAccount,
+				enabledForRequest: tt.args.enabledForRequest,
+			}
+			isEventAllowed := evData.isEventAllowed()
+			assert.Equal(t, tt.want, isEventAllowed)
+		})
+	}
+}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index bef48be1..661233ec 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -2398,10 +2398,12 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest},
 		Account: config.Account{
-			ID:            "testaccount",
-			EventsEnabled: spec.EventsEnabled,
-			DebugAllow:    true,
-			Validations:   spec.AccountConfigBidValidation,
+			ID: "testaccount",
+			Events: config.Events{
+				Enabled: &spec.EventsEnabled,
+			},
+			DebugAllow:  true,
+			Validations: spec.AccountConfigBidValidation,
 		},
 		UserSyncs:     mockIdFetcher(spec.IncomingRequest.Usersyncs),
 		ImpExtInfoMap: impExtInfoMap,
@@ -5313,7 +5315,7 @@ type validatingBidder struct {
 	mockResponses map[string]bidderResponse
 }
 
-func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBids []*entities.PbsOrtbSeatBid, errs []error) {
+func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBids []*entities.PbsOrtbSeatBid, errs []error) {
 	if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok {
 		if expectedRequest != nil {
 			if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) {
@@ -5371,7 +5373,7 @@ type capturingRequestBidder struct {
 	req *openrtb2.BidRequest
 }
 
-func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (seatBid []*entities.PbsOrtbSeatBid, errs []error) {
+func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBid []*entities.PbsOrtbSeatBid, errs []error) {
 	b.req = bidderRequest.BidRequest
 	return []*entities.PbsOrtbSeatBid{{}}, nil
 }
@@ -5478,7 +5480,7 @@ func (e *emptyUsersync) HasAnyLiveSyncs() bool {
 
 type panicingAdapter struct{}
 
-func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor) (posb []*entities.PbsOrtbSeatBid, errs []error) {
+func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (posb []*entities.PbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }