Repository: prebid/prebid-server
Pull request #2339 (2022-08-04 04:20:30)

Add alternatebiddercodes in Prebid request ext
w.r.t https://github.com/prebid/prebid-server/issues/2174#issuecomment-1190736031

This changes would make alternatebiddercodes from PBS config formally part of Prebid ORTB extensions.

- Incoming ORTB would contain:
```
ext.prebid.alternatebiddercodes.enabled: true/false
ext.prebid.alternatebiddercodes.bidders.BIDDER.enabled: true/false
ext.prebid.alternatebiddercodes.bidders.BIDDER.allowedbiddercodes: [array, of, strings]
```
- This would override the account-level config
- If the request doesn't specify these values, then PBS-core would merge the account config into these values
- Before sending to each adapter, PBS-core would need to remove entries that don't belong to that bidder. i.e. they would only receive ext.prebid.alternatebiddercodes.adapters.ME
- Everything else is up to the adapter. They can ignore or use at will.

Ex. s2s config on page and incoming PBS request
```
pbjs.setConfig({
    s2sConfig: {
        extPrebid: {
            "alternatebiddercodes": {
                "enabled": true,
                "bidders": {
                    "pubmatic": {
                        "enabled": true,
                        "allowedbiddercodes": [
                            "groupm"
                        ]
                    }
                }
            }
        }
    }
});
```

```
{
   ...
   "ext": {
       "prebid": {
           "alternatebiddercodes": {
                "enabled": true,
                "bidders": {
                    "pubmatic": {
                        "enabled": true,
                        "allowedbiddercodes": [
                            "groupm"
                        ]
                    }
                }
            }
       }
   }
}
```

More details at https://github.com/prebid/prebid-server/issues/2174

VeronikaSolovei9: Code looks good, local testing looks good too. 
pm-nilesh-chate: Hi @bsardo, I've address the comments. Could you review the same.
pm-nilesh-chate: @bsardo I've address the comments. Could you review the same.

Thank you for the critical inputs. A good learning for my future PRs :)
pm-nilesh-chate: Addressed the review comments. Had to rebase and force push to fix the master merge test failures.

@bsardo Could you please review the latest changes.

Thanks

abhinavsinha001: What is the holdup here can we get these changes merged? 
We want to start using this functionality.
bsardo: @abhinavsinha001 I'm waiting for a response on this https://github.com/prebid/prebid-server/issues/2174#issuecomment-1242045987
abhinavsinha001: Thanks @bsardo for pointing to the comment.
@bretg can you please help unblock here.
pm-nilesh-chate: Hi @bsardo, I've addressed the comments. Please review the same. Fyi, last commit (master merge) is to address Trivy's check.

Patch:
diff --git a/config/accounts.go b/config/accounts.go
index e5bae8af..75319001 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -1,8 +1,6 @@
 package config
 
 import (
-	"fmt"
-
 	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -20,18 +18,18 @@ 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    AlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"`
+	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"`
 }
 
 // CookieSync represents the account-level defaults for the cookie sync endpoint.
@@ -223,51 +221,3 @@ func (a *AccountIntegration) GetByIntegrationType(integrationType IntegrationTyp
 
 	return integrationEnabled
 }
-
-type AlternateBidderCodes struct {
-	Enabled bool                                   `mapstructure:"enabled" json:"enabled"`
-	Bidders map[string]AdapterAlternateBidderCodes `mapstructure:"bidders" json:"bidders"`
-}
-
-type AdapterAlternateBidderCodes struct {
-	Enabled            bool     `mapstructure:"enabled" json:"enabled"`
-	AllowedBidderCodes []string `mapstructure:"allowedbiddercodes" json:"allowedbiddercodes"`
-}
-
-func (bidderCodes *AlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) {
-	const ErrAlternateBidderNotDefined = "alternateBidderCodes not defined for adapter %q, rejecting bids for %q"
-
-	if alternateBidder == "" || bidder == alternateBidder {
-		return true, nil
-	}
-
-	if !bidderCodes.Enabled {
-		return false, nil
-	}
-
-	if bidderCodes.Bidders == nil {
-		return false, fmt.Errorf(ErrAlternateBidderNotDefined, bidder, alternateBidder)
-	}
-
-	adapterCfg, ok := bidderCodes.Bidders[bidder]
-	if !ok {
-		return false, fmt.Errorf(ErrAlternateBidderNotDefined, bidder, alternateBidder)
-	}
-
-	if !adapterCfg.Enabled {
-		// config has bidder entry but is not enabled, report it
-		return false, fmt.Errorf("alternateBidderCodes disabled for %q, rejecting bids for %q", bidder, alternateBidder)
-	}
-
-	if adapterCfg.AllowedBidderCodes == nil || (len(adapterCfg.AllowedBidderCodes) == 1 && adapterCfg.AllowedBidderCodes[0] == "*") {
-		return true, nil
-	}
-
-	for _, code := range adapterCfg.AllowedBidderCodes {
-		if alternateBidder == code {
-			return true, nil
-		}
-	}
-
-	return false, fmt.Errorf("invalid biddercode %q sent by adapter %q", alternateBidder, bidder)
-}
diff --git a/config/bidderinfo.go b/config/bidderinfo.go
index 74122bdf..70e1f89f 100644
--- a/config/bidderinfo.go
+++ b/config/bidderinfo.go
@@ -9,6 +9,7 @@ import (
 	"github.com/prebid/prebid-server/util/sliceutil"
 	"io/ioutil"
 	"log"
+	"path/filepath"
 	"strings"
 	"text/template"
 
@@ -170,38 +171,66 @@ func (bi BidderInfo) IsEnabled() bool {
 	return !bi.Disabled
 }
 
-// LoadBidderInfo parses all static/bidder-info/{bidder}.yaml files from the file system.
-func LoadBidderInfo(path string) (BidderInfos, error) {
-	bidderConfigs, err := ioutil.ReadDir(path)
+type InfoReader interface {
+	Read() (map[string][]byte, error)
+}
+
+type InfoReaderFromDisk struct {
+	Path string
+}
+
+func (r InfoReaderFromDisk) Read() (map[string][]byte, error) {
+	bidderConfigs, err := ioutil.ReadDir(r.Path)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	infos := BidderInfos{}
-
+	bidderInfos := make(map[string][]byte)
 	for _, bidderConfig := range bidderConfigs {
 		if bidderConfig.IsDir() {
 			continue //ignore directories
 		}
 		fileName := bidderConfig.Name()
-		filePath := fmt.Sprintf("%v/%v", path, fileName)
+		filePath := filepath.Join(r.Path, fileName)
 		data, err := ioutil.ReadFile(filePath)
 		if err != nil {
 			return nil, err
 		}
+		bidderInfos[fileName] = data
+	}
+	return bidderInfos, nil
+}
 
-		info := BidderInfo{}
-		if err := yaml.Unmarshal(data, &info); err != nil {
-			return nil, fmt.Errorf("error parsing yaml for bidder %s: %v", fileName, err)
-		}
+func LoadBidderInfoFromDisk(path string) (BidderInfos, error) {
+	bidderInfoReader := InfoReaderFromDisk{Path: path}
+	return LoadBidderInfo(bidderInfoReader)
+}
 
+func LoadBidderInfo(reader InfoReader) (BidderInfos, error) {
+	return processBidderInfos(reader, openrtb_ext.NormalizeBidderName)
+}
+
+func processBidderInfos(reader InfoReader, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) {
+	bidderConfigs, err := reader.Read()
+	if err != nil {
+		return nil, fmt.Errorf("error loading bidders data")
+	}
+
+	infos := BidderInfos{}
+
+	for fileName, data := range bidderConfigs {
 		bidderName := strings.Split(fileName, ".")
 		if len(bidderName) == 2 && bidderName[1] == "yaml" {
-			// all bidder names loaded from the file system should be in lowercase
-			// to match bidder names in Viper config bidder infos
-			// this will prevent from missing bidder config overrides
-			// in func applyBidderInfoConfigOverrides
-			infos[(strings.ToLower(bidderName[0]))] = info
+			normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0])
+			if !bidderNameExists {
+				return nil, fmt.Errorf("error parsing config for bidder %s: unknown bidder", fileName)
+			}
+			info := BidderInfo{}
+			if err := yaml.Unmarshal(data, &info); err != nil {
+				return nil, fmt.Errorf("error parsing config for bidder %s: %v", fileName, err)
+			}
+
+			infos[string(normalizedBidderName)] = info
 		}
 	}
 	return infos, nil
@@ -349,9 +378,13 @@ func validateSyncer(bidderInfo BidderInfo) error {
 	return nil
 }
 
-func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos BidderInfos) (BidderInfos, error) {
+func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) {
 	for bidderName, bidderInfo := range configBidderInfos {
-		if fsBidderCfg, exists := fsBidderInfos[bidderName]; exists {
+		normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName)
+		if !bidderNameExists {
+			return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName)
+		}
+		if fsBidderCfg, exists := fsBidderInfos[string(normalizedBidderName)]; exists {
 			bidderInfo.Syncer = bidderInfo.Syncer.Override(fsBidderCfg.Syncer)
 
 			if bidderInfo.Endpoint == "" && len(fsBidderCfg.Endpoint) > 0 {
@@ -436,7 +469,9 @@ func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos
 				glog.Warningf("adapters.%s.usersync_url is deprecated and will be removed in a future version, please update to the latest user sync config values", strings.ToLower(bidderName))
 			}
 
-			fsBidderInfos[bidderName] = bidderInfo
+			fsBidderInfos[string(normalizedBidderName)] = bidderInfo
+		} else {
+			return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName)
 		}
 	}
 	return fsBidderInfos, nil
diff --git a/config/config.go b/config/config.go
index a424a3f1..fc57bdc7 100644
--- a/config/config.go
+++ b/config/config.go
@@ -617,7 +617,7 @@ func (cfg *TimeoutNotification) validate(errs []error) []error {
 }
 
 // New uses viper to get our server configurations.
-func New(v *viper.Viper, bidderInfos BidderInfos) (*Configuration, error) {
+func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (*Configuration, error) {
 	var c Configuration
 	if err := v.Unmarshal(&c); err != nil {
 		return nil, fmt.Errorf("viper failed to unmarshal app config: %v", err)
@@ -700,7 +700,7 @@ func New(v *viper.Viper, bidderInfos BidderInfos) (*Configuration, error) {
 	// Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs.
 	resolvedStoredRequestsConfig(&c)
 
-	mergedBidderInfos, err := applyBidderInfoConfigOverrides(c.BidderInfos, bidderInfos)
+	mergedBidderInfos, err := applyBidderInfoConfigOverrides(c.BidderInfos, bidderInfos, normalizeBidderName)
 	if err != nil {
 		return nil, err
 	}
diff --git a/config/test/bidder-info/someBidder.yaml b/config/test/bidder-info-valid/stroeerCore.yaml
similarity index 100%
rename from config/test/bidder-info/someBidder.yaml
rename to config/test/bidder-info-valid/stroeerCore.yaml
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 479c0256..a9fe874e 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -54,7 +54,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 config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error)
+	requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) ([]*pbsOrtbSeatBid, []error)
 }
 
 // bidRequestOptions holds additional options for bid request execution to maintain clean code and reasonable number of parameters
@@ -154,7 +154,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 config.AlternateBidderCodes) ([]*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) ([]*pbsOrtbSeatBid, []error) {
 
 	var reqData []*adapters.RequestData
 	var errs []error
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index 504b2cf3..d48c3e8f 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -8,9 +8,9 @@ import (
 
 	"github.com/mxmCherry/openrtb/v16/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
-	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/experiment/adscert"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	goCurrency "golang.org/x/text/currency"
 )
 
@@ -29,7 +29,7 @@ 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 config.AlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
+func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes) ([]*pbsOrtbSeatBid, []error) {
 	seatBids, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes)
 	for _, seatBid := range seatBids {
 		if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index f1a9a7b8..40fc4a2a 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -286,7 +286,16 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	} else {
 		// List of bidders we have requests for.
 		liveAdapters = listBiddersWithRequests(bidderRequests)
-		adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, r.Account.AlternateBidderCodes, requestExt.Prebid.Experiment)
+
+		//This will be used validate bids
+		var alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes
+		if requestExt != nil && requestExt.Prebid.AlternateBidderCodes != nil {
+			alternateBidderCodes = *requestExt.Prebid.AlternateBidderCodes
+		} else if r.Account.AlternateBidderCodes != nil {
+			alternateBidderCodes = *r.Account.AlternateBidderCodes
+		}
+
+		adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExt.Prebid.Experiment)
 	}
 
 	var auc *auction
@@ -494,7 +503,7 @@ func (e *exchange) getAllBids(
 	accountDebugAllowed bool,
 	globalPrivacyControlHeader string,
 	headerDebugAllowed bool,
-	alternateBidderCodes config.AlternateBidderCodes,
+	alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes,
 	experiment *openrtb_ext.Experiment) (
 	map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
 	map[openrtb_ext.BidderName]*seatResponseExtra, bool) {
diff --git a/exchange/utils.go b/exchange/utils.go
index 96e5e2a9..9007e20e 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -237,7 +237,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,
 
 		sChainWriter.Write(&reqCopy, bidder)
 
-		reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt)
+		reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt, auctionRequest.Account.AlternateBidderCodes)
 		if err != nil {
 			return nil, []error{err}
 		}
@@ -273,8 +273,15 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,
 	return bidderRequests, errs
 }
 
-func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage) (json.RawMessage, error) {
-	if len(requestExt) == 0 || requestExtParsed == nil {
+func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) (json.RawMessage, error) {
+	// Resolve alternatebiddercode for current bidder
+	var reqABC *openrtb_ext.ExtAlternateBidderCodes
+	if len(requestExt) != 0 && requestExtParsed != nil && requestExtParsed.Prebid.AlternateBidderCodes != nil {
+		reqABC = requestExtParsed.Prebid.AlternateBidderCodes
+	}
+	alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC)
+
+	if (len(requestExt) == 0 || requestExtParsed == nil) && alternateBidderCodes == nil {
 		return json.RawMessage(``), nil
 	}
 
@@ -287,11 +294,15 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request
 	// Copy Allowed Fields
 	// Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary
 	prebid := openrtb_ext.ExtRequestPrebid{
-		Integration:         requestExtParsed.Prebid.Integration,
-		Channel:             requestExtParsed.Prebid.Channel,
-		Debug:               requestExtParsed.Prebid.Debug,
-		CurrencyConversions: requestExtParsed.Prebid.CurrencyConversions,
-		BidderParams:        bidderParams,
+		BidderParams:         bidderParams,
+		AlternateBidderCodes: alternateBidderCodes,
+	}
+
+	if requestExtParsed != nil {
+		prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions
+		prebid.Integration = requestExtParsed.Prebid.Integration
+		prebid.Channel = requestExtParsed.Prebid.Channel
+		prebid.Debug = requestExtParsed.Prebid.Debug
 	}
 
 	// Marshal New Prebid Object
@@ -302,14 +313,44 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request
 
 	// Update Ext With Prebid Json
 	extMap := make(map[string]json.RawMessage)
-	if err := json.Unmarshal(requestExt, &extMap); err != nil {
-		return nil, err
+	if len(requestExt) != 0 {
+		if err := json.Unmarshal(requestExt, &extMap); err != nil {
+			return nil, err
+		}
 	}
 	extMap["prebid"] = prebidJson
 
 	return json.Marshal(extMap)
 }
 
+func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes {
+	if reqABC != nil {
+		alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{
+			Enabled: reqABC.Enabled,
+		}
+		if bidderCodes, ok := reqABC.Bidders[bidder]; ok {
+			alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+				bidder: bidderCodes,
+			}
+		}
+		return alternateBidderCodes
+	}
+
+	if accABC != nil {
+		alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{
+			Enabled: accABC.Enabled,
+		}
+		if bidderCodes, ok := accABC.Bidders[bidder]; ok {
+			alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+				bidder: bidderCodes,
+			}
+		}
+		return alternateBidderCodes
+	}
+
+	return nil
+}
+
 // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext.
 // This prevents a Bidder from using these values to figure out who else is involved in the Auction.
 func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) {
diff --git a/main.go b/main.go
index bdd79f11..a8326666 100644
--- a/main.go
+++ b/main.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/router"
 	"github.com/prebid/prebid-server/server"
 	"github.com/prebid/prebid-server/util/task"
@@ -30,7 +31,7 @@ func main() {
 		glog.Exitf("Unable to build configuration directory path: %v", err)
 	}
 
-	bidderInfos, err := config.LoadBidderInfo(bidderInfoPath)
+	bidderInfos, err := config.LoadBidderInfoFromDisk(bidderInfoPath)
 	if err != nil {
 		glog.Exitf("Unable to load bidder configurations: %v", err)
 	}
@@ -59,7 +60,7 @@ const infoDirectory = "./static/bidder-info"
 func loadConfig(bidderInfos config.BidderInfos) (*config.Configuration, error) {
 	v := viper.New()
 	config.SetupViper(v, configFileName, bidderInfos)
-	return config.New(v, bidderInfos)
+	return config.New(v, bidderInfos, openrtb_ext.NormalizeBidderName)
 }
 
 func serve(cfg *config.Configuration) error {
diff --git a/openrtb_ext/alternatebiddercodes.go b/openrtb_ext/alternatebiddercodes.go
new file mode 100644
index 00000000..49291a3e
--- /dev/null
+++ b/openrtb_ext/alternatebiddercodes.go
@@ -0,0 +1,58 @@
+package openrtb_ext
+
+import "fmt"
+
+// ExtAlternateBidderCodes defines list of alternate bidder codes allowed by adatpers. This overrides host level configs.
+type ExtAlternateBidderCodes struct {
+	Enabled bool                                      `mapstructure:"enabled" json:"enabled"`
+	Bidders map[string]ExtAdapterAlternateBidderCodes `mapstructure:"bidders" json:"bidders"`
+}
+
+type ExtAdapterAlternateBidderCodes struct {
+	Enabled            bool     `mapstructure:"enabled" json:"enabled"`
+	AllowedBidderCodes []string `mapstructure:"allowedbiddercodes" json:"allowedbiddercodes"`
+}
+
+func (bidderCodes *ExtAlternateBidderCodes) IsValidBidderCode(bidder, alternateBidder string) (bool, error) {
+	if alternateBidder == "" || bidder == alternateBidder {
+		return true, nil
+	}
+
+	if !bidderCodes.Enabled {
+		return false, alternateBidderDisabledError(bidder, alternateBidder)
+	}
+
+	if bidderCodes.Bidders == nil {
+		return false, alternateBidderNotDefinedError(bidder, alternateBidder)
+	}
+
+	adapterCfg, ok := bidderCodes.Bidders[bidder]
+	if !ok {
+		return false, alternateBidderNotDefinedError(bidder, alternateBidder)
+	}
+
+	if !adapterCfg.Enabled {
+		// config has bidder entry but is not enabled, report it
+		return false, alternateBidderDisabledError(bidder, alternateBidder)
+	}
+
+	if adapterCfg.AllowedBidderCodes == nil || (len(adapterCfg.AllowedBidderCodes) == 1 && adapterCfg.AllowedBidderCodes[0] == "*") {
+		return true, nil
+	}
+
+	for _, code := range adapterCfg.AllowedBidderCodes {
+		if alternateBidder == code {
+			return true, nil
+		}
+	}
+
+	return false, fmt.Errorf("invalid biddercode %q sent by adapter %q", alternateBidder, bidder)
+}
+
+func alternateBidderDisabledError(bidder, alternateBidder string) error {
+	return fmt.Errorf("alternateBidderCodes disabled for %q, rejecting bids for %q", bidder, alternateBidder)
+}
+
+func alternateBidderNotDefinedError(bidder, alternateBidder string) error {
+	return fmt.Errorf("alternateBidderCodes not defined for adapter %q, rejecting bids for %q", bidder, alternateBidder)
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index b3807c5d..cb33b0bb 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -58,6 +58,9 @@ type ExtRequestPrebid struct {
 	// passing of personally identifiable information doesn't constitute a sale per CCPA law.
 	// The array may contain a single sstar ('*') entry to represent all bidders.
 	NoSale []string `json:"nosale,omitempty"`
+
+	//AlternateBidderCodes is populated with host's AlternateBidderCodes config if not defined in request
+	AlternateBidderCodes *ExtAlternateBidderCodes `json:"alternatebiddercodes,omitempty"`
 }
 
 // Experiment defines if experimental features are available for the request


Test patch:
diff --git a/config/accounts_test.go b/config/accounts_test.go
index be9f69d2..7a78c8ed 100644
--- a/config/accounts_test.go
+++ b/config/accounts_test.go
@@ -1,7 +1,6 @@
 package config
 
 import (
-	"errors"
 	"testing"
 
 	"github.com/prebid/go-gdpr/consentconstants"
@@ -711,174 +710,3 @@ func TestBasicEnforcementVendor(t *testing.T) {
 		assert.Equal(t, tt.wantBasicVendorSet, present, tt.description)
 	}
 }
-
-func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) {
-	type fields struct {
-		Enabled bool
-		Bidders map[string]AdapterAlternateBidderCodes
-	}
-	type args struct {
-		bidder          string
-		alternateBidder string
-	}
-	tests := []struct {
-		name        string
-		fields      fields
-		args        args
-		wantIsValid bool
-		wantErr     error
-	}{
-		{
-			name:        "alternateBidder is not set/blank (default non-extra bid case)",
-			wantIsValid: true,
-		},
-		{
-			name: "alternateBidder and bidder are same (default non-extra bid case with seat's alternateBidder explicitly set)",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "pubmatic",
-			},
-			wantIsValid: true,
-		},
-		{
-			name: "account.alternatebiddercodes config not defined (default, reject bid)",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			wantIsValid: false,
-		},
-		{
-			name: "account.alternatebiddercodes config enabled but adapter config not defined",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields:      fields{Enabled: true},
-			wantIsValid: false,
-			wantErr:     errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`),
-		},
-		{
-			name: "account.alternatebiddercodes config enabled but adapter config is not available",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"appnexus": {},
-				},
-			},
-			wantIsValid: false,
-			wantErr:     errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`),
-		},
-		{
-			name: "account.alternatebiddercodes config enabled but adapter config is disabled",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"pubmatic": {Enabled: false},
-				},
-			},
-			wantIsValid: false,
-			wantErr:     errors.New(`alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`),
-		},
-		{
-			name: "account.alternatebiddercodes and adapter config enabled but adapter config does not have allowedBidderCodes defined",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"pubmatic": {Enabled: true},
-				},
-			},
-			wantIsValid: true,
-		},
-		{
-			name: "allowedBidderCodes is *",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"pubmatic": {
-						Enabled:            true,
-						AllowedBidderCodes: []string{"*"},
-					},
-				},
-			},
-			wantIsValid: true,
-		},
-		{
-			name: "allowedBidderCodes is in the list",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"pubmatic": {
-						Enabled:            true,
-						AllowedBidderCodes: []string{"groupm"},
-					},
-				},
-			},
-			wantIsValid: true,
-		},
-		{
-			name: "allowedBidderCodes is not in the list",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"pubmatic": {
-						Enabled:            true,
-						AllowedBidderCodes: []string{"xyz"},
-					},
-				},
-			},
-			wantIsValid: false,
-			wantErr:     errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`),
-		},
-		{
-			name: "account.alternatebiddercodes and adapter config enabled but adapter config has allowedBidderCodes list empty",
-			args: args{
-				bidder:          "pubmatic",
-				alternateBidder: "groupm",
-			},
-			fields: fields{
-				Enabled: true,
-				Bidders: map[string]AdapterAlternateBidderCodes{
-					"pubmatic": {Enabled: true, AllowedBidderCodes: []string{}},
-				},
-			},
-			wantIsValid: false,
-			wantErr:     errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`),
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			a := &AlternateBidderCodes{
-				Enabled: tt.fields.Enabled,
-				Bidders: tt.fields.Bidders,
-			}
-			gotIsValid, gotErr := a.IsValidBidderCode(tt.args.bidder, tt.args.alternateBidder)
-			assert.Equal(t, tt.wantIsValid, gotIsValid)
-			assert.Equal(t, tt.wantErr, gotErr)
-		})
-	}
-}
diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go
index 85e15323..462e920f 100644
--- a/config/bidderinfo_test.go
+++ b/config/bidderinfo_test.go
@@ -10,7 +10,12 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-const testInfoFilesPath = "./test/bidder-info"
+const testInfoFilesPathValid = "./test/bidder-info-valid"
+const testSimpleYAML = `
+maintainer:
+  email: "some-email@domain.com"
+gvlVendorID: 42
+`
 const fullBidderYAMLConfig = `
 maintainer:
   email: "some-email@domain.com"
@@ -36,14 +41,14 @@ endpointCompression: "GZIP"
 `
 
 func TestLoadBidderInfoFromDisk(t *testing.T) {
-	// should appear in result in lowercase
-	bidder := "somebidder"
+	// should appear in result in mixed case
+	bidder := "stroeerCore"
 	trueValue := true
 
 	adapterConfigs := make(map[string]Adapter)
 	adapterConfigs[strings.ToLower(bidder)] = Adapter{}
 
-	infos, err := LoadBidderInfo(testInfoFilesPath)
+	infos, err := LoadBidderInfoFromDisk(testInfoFilesPathValid)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -84,6 +89,80 @@ func TestLoadBidderInfoFromDisk(t *testing.T) {
 	assert.Equal(t, expected, infos)
 }
 
+func TestProcessBidderInfo(t *testing.T) {
+	testCases := []struct {
+		description         string
+		bidderInfos         map[string][]byte
+		expectedBidderInfos BidderInfos
+		expectError         string
+	}{
+		{
+			description: "Valid bidder info",
+			bidderInfos: map[string][]byte{
+				"bidderA.yaml": []byte(testSimpleYAML),
+			},
+			expectedBidderInfos: BidderInfos{
+				"bidderA": BidderInfo{
+					Maintainer: &MaintainerInfo{
+						Email: "some-email@domain.com",
+					},
+					GVLVendorID: 42,
+				},
+			},
+			expectError: "",
+		},
+		{
+			description: "Bidder doesn't exist in bidder info list",
+			bidderInfos: map[string][]byte{
+				"unknown.yaml": []byte(testSimpleYAML),
+			},
+			expectedBidderInfos: nil,
+			expectError:         "error parsing config for bidder unknown.yaml",
+		},
+		{
+			description: "Invalid bidder config",
+			bidderInfos: map[string][]byte{
+				"bidderA.yaml": []byte("invalid bidder config"),
+			},
+			expectedBidderInfos: nil,
+			expectError:         "error parsing config for bidder bidderA.yaml",
+		},
+	}
+	for _, test := range testCases {
+		reader := StubInfoReader{test.bidderInfos}
+		bidderInfos, err := processBidderInfos(reader, mockNormalizeBidderName)
+		if test.expectError != "" {
+			assert.ErrorContains(t, err, test.expectError, "")
+		} else {
+			assert.Equal(t, test.expectedBidderInfos, bidderInfos, "incorrect bidder infos for test case: %s", test.description)
+		}
+
+	}
+
+}
+
+type StubInfoReader struct {
+	mockBidderInfos map[string][]byte
+}
+
+func (r StubInfoReader) Read() (map[string][]byte, error) {
+	return r.mockBidderInfos, nil
+}
+
+var testBidderNames = map[string]openrtb_ext.BidderName{
+	"biddera": openrtb_ext.BidderName("bidderA"),
+	"bidderb": openrtb_ext.BidderName("bidderB"),
+	"bidder1": openrtb_ext.BidderName("bidder1"),
+	"bidder2": openrtb_ext.BidderName("bidder2"),
+	"a":       openrtb_ext.BidderName("a"),
+}
+
+func mockNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) {
+	nameLower := strings.ToLower(name)
+	bidderName, exists := testBidderNames[nameLower]
+	return bidderName, exists
+}
+
 func TestToGVLVendorIDMap(t *testing.T) {
 	givenBidderInfos := BidderInfos{
 		"bidderA": BidderInfo{Disabled: false, GVLVendorID: 0},
@@ -105,7 +184,7 @@ const bidderInfoRelativePath = "../static/bidder-info"
 // TestBidderInfoFiles ensures each bidder has a valid static/bidder-info/bidder.yaml file. Validation is performed directly
 // against the file system with separate yaml unmarshalling from the LoadBidderInfo func.
 func TestBidderInfoFiles(t *testing.T) {
-	_, err := LoadBidderInfo(bidderInfoRelativePath)
+	_, err := LoadBidderInfoFromDisk(bidderInfoRelativePath)
 	if err != nil {
 		assert.Fail(t, err.Error(), "Errors in bidder info files")
 	}
@@ -687,7 +766,7 @@ func TestApplyBidderInfoConfigSyncerOverrides(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos)
+		bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName)
 		if test.expectedError == "" {
 			assert.NoError(t, resultErr, test.description+":err")
 			assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description+":result")
@@ -849,17 +928,43 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) {
 		},
 	}
 	for _, test := range testCases {
-		bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos)
+		bidderInfos, resultErr := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName)
 		assert.NoError(t, resultErr, test.description+":err")
 		assert.Equal(t, test.expectedBidderInfos, bidderInfos, test.description+":result")
 	}
 }
 
+func TestApplyBidderInfoConfigOverridesInvalid(t *testing.T) {
+	var testCases = []struct {
+		description            string
+		givenFsBidderInfos     BidderInfos
+		givenConfigBidderInfos BidderInfos
+		expectedError          string
+		expectedBidderInfos    BidderInfos
+	}{
+		{
+			description:            "Bidder doesn't exists in bidder list",
+			givenConfigBidderInfos: BidderInfos{"unknown": {Syncer: &Syncer{Key: "override"}}},
+			expectedError:          "error setting configuration for bidder unknown: unknown bidder",
+		},
+		{
+			description:            "Bidder doesn't exists in file system",
+			givenFsBidderInfos:     BidderInfos{"unknown": {Endpoint: "original"}},
+			givenConfigBidderInfos: BidderInfos{"bidderA": {Syncer: &Syncer{Key: "override"}}},
+			expectedError:          "error finding configuration for bidder bidderA: unknown bidder",
+		},
+	}
+	for _, test := range testCases {
+		_, err := applyBidderInfoConfigOverrides(test.givenConfigBidderInfos, test.givenFsBidderInfos, mockNormalizeBidderName)
+		assert.ErrorContains(t, err, test.expectedError, test.description+":err")
+	}
+}
+
 func TestReadFullYamlBidderConfig(t *testing.T) {
-	bidder := "someBidder"
+	bidder := "bidderA"
 	bidderInf := BidderInfo{}
 	err := yaml.Unmarshal([]byte(fullBidderYAMLConfig), &bidderInf)
-	actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}})
+	actualBidderInfo, err := applyBidderInfoConfigOverrides(BidderInfos{bidder: bidderInf}, BidderInfos{bidder: {Syncer: &Syncer{Supports: []string{"iframe"}}}}, mockNormalizeBidderName)
 
 	assert.NoError(t, err, "Error wasn't expected")
 
diff --git a/config/config_test.go b/config/config_test.go
index 27077ad7..e2a97180 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -442,7 +442,7 @@ func TestFullConfig(t *testing.T) {
 	SetupViper(v, "", bidderInfos)
 	v.SetConfigType("yaml")
 	v.ReadConfig(bytes.NewBuffer(fullConfig))
-	cfg, err := New(v, bidderInfos)
+	cfg, err := New(v, bidderInfos, mockNormalizeBidderName)
 	assert.NoError(t, err, "Setting up config should work but it doesn't")
 	cmpStrings(t, "cookie domain", cfg.HostCookie.Domain, "cookies.prebid.org")
 	cmpStrings(t, "cookie name", cfg.HostCookie.CookieName, "userid")
@@ -691,7 +691,7 @@ func TestMigrateConfig(t *testing.T) {
 	v.SetConfigType("yaml")
 	v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig))
 	migrateConfig(v)
-	cfg, err := New(v, bidderInfos)
+	cfg, err := New(v, bidderInfos, mockNormalizeBidderName)
 	assert.NoError(t, err, "Setting up config should work but it doesn't")
 	cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled)
 	cmpStrings(t, "stored_requests.filesystem.path", "/somepath", cfg.StoredRequests.Files.Path)
@@ -1753,7 +1753,7 @@ func TestNewCallsRequestValidation(t *testing.T) {
 			`request_validation:
     ipv4_private_networks: ` + test.privateIPNetworks)))
 
-		result, resultErr := New(v, bidderInfos)
+		result, resultErr := New(v, bidderInfos, mockNormalizeBidderName)
 
 		if test.expectedError == "" {
 			assert.NoError(t, resultErr, test.description+":err")
@@ -1788,7 +1788,7 @@ func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) {
 	SetupViper(v, "", bidderInfos)
 	v.Set("gdpr.default_value", "0")
 	v.SetConfigType("yaml")
-	cfg, err := New(v, bidderInfos)
+	cfg, err := New(v, bidderInfos, mockNormalizeBidderName)
 	assert.NoError(t, err, "Setting up config should work but it doesn't")
 	return cfg, v
 }
@@ -1858,7 +1858,7 @@ func TestSpecialFeature1VendorExceptionMap(t *testing.T) {
 		SetupViper(v, "", bidderInfos)
 		v.SetConfigType("yaml")
 		v.ReadConfig(bytes.NewBuffer(config))
-		cfg, err := New(v, bidderInfos)
+		cfg, err := New(v, bidderInfos, mockNormalizeBidderName)
 		assert.NoError(t, err, "Setting up config error", tt.description)
 
 		assert.Equal(t, tt.wantVendorExceptions, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description)
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index d6a74ac7..47f68b60 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -106,7 +106,7 @@ func TestSingleBidder(t *testing.T) {
 			addCallSignHeader:   false,
 			bidAdjustments:      bidAdjustments,
 		}
-		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
 
@@ -228,7 +228,7 @@ func TestSingleBidderGzip(t *testing.T) {
 			addCallSignHeader:   false,
 			bidAdjustments:      bidAdjustments,
 		}
-		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+		seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
 
@@ -328,7 +328,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 
 	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
 		{
@@ -380,7 +380,7 @@ func TestSetGPCHeader(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -430,7 +430,7 @@ func TestSetGPCHeaderNil(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 
 	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
 		{
@@ -500,7 +500,7 @@ func TestMultiBidder(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 
 	if len(seatBids) != 1 {
 		t.Fatalf("SeatBid should exist, because bids exist.")
@@ -879,7 +879,7 @@ func TestMultiCurrencies(t *testing.T) {
 				addCallSignHeader:   false,
 				bidAdjustments:      bidAdjustments,
 			},
-			config.AlternateBidderCodes{},
+			openrtb_ext.ExtAlternateBidderCodes{},
 		)
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
@@ -1037,7 +1037,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 				addCallSignHeader:   false,
 				bidAdjustments:      bidAdjustments,
 			},
-			config.AlternateBidderCodes{},
+			openrtb_ext.ExtAlternateBidderCodes{},
 		)
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
@@ -1214,7 +1214,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 				addCallSignHeader:   false,
 				bidAdjustments:      bidAdjustments,
 			},
-			config.AlternateBidderCodes{},
+			openrtb_ext.ExtAlternateBidderCodes{},
 		)
 		assert.Len(t, seatBids, 1)
 		seatBid := seatBids[0]
@@ -1530,7 +1530,7 @@ func TestMobileNativeTypes(t *testing.T) {
 				addCallSignHeader:   false,
 				bidAdjustments:      bidAdjustments,
 			},
-			config.AlternateBidderCodes{},
+			openrtb_ext.ExtAlternateBidderCodes{},
 		)
 		assert.Len(t, seatBids, 1)
 
@@ -1648,7 +1648,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 				addCallSignHeader:   false,
 				bidAdjustments:      bidAdjustments,
 			},
-			config.AlternateBidderCodes{},
+			openrtb_ext.ExtAlternateBidderCodes{},
 		)
 		assert.Len(t, seatBids, 1)
 
@@ -1675,7 +1675,7 @@ func TestErrorReporting(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	bids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 	if bids != nil {
 		t.Errorf("There should be no seatbid if no http requests are returned.")
 	}
@@ -1907,7 +1907,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	_, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	_, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 
 	// Assert no errors
 	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
@@ -2150,7 +2150,7 @@ func TestRequestBidsWithAdsCertsSigner(t *testing.T) {
 		addCallSignHeader:   true,
 		bidAdjustments:      bidAdjustments,
 	}
-	_, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	_, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 
 	assert.Empty(t, errs, "no errors should be returned")
 }
@@ -2365,9 +2365,9 @@ func TestExtraBid(t *testing.T) {
 	}
 
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
-		config.AlternateBidderCodes{
+		openrtb_ext.ExtAlternateBidderCodes{
 			Enabled: true,
-			Bidders: map[string]config.AdapterAlternateBidderCodes{
+			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 				string(openrtb_ext.BidderPubmatic): {
 					Enabled:            true,
 					AllowedBidderCodes: []string{"groupm"},
@@ -2477,9 +2477,9 @@ func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
 	}
 
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
-		config.AlternateBidderCodes{
+		openrtb_ext.ExtAlternateBidderCodes{
 			Enabled: true,
-			Bidders: map[string]config.AdapterAlternateBidderCodes{
+			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 				string(openrtb_ext.BidderPubmatic): {
 					Enabled:            true,
 					AllowedBidderCodes: []string{"groupm-allowed"},
@@ -2586,9 +2586,9 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 	}
 
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
-		config.AlternateBidderCodes{
+		openrtb_ext.ExtAlternateBidderCodes{
 			Enabled: true,
-			Bidders: map[string]config.AdapterAlternateBidderCodes{
+			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 				string(openrtb_ext.BidderPubmatic): {
 					Enabled:            true,
 					AllowedBidderCodes: []string{"groupm"},
@@ -2697,9 +2697,9 @@ func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
 	}
 
 	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
-		config.AlternateBidderCodes{
+		openrtb_ext.ExtAlternateBidderCodes{
 			Enabled: true,
-			Bidders: map[string]config.AdapterAlternateBidderCodes{
+			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 				string(openrtb_ext.BidderPubmatic): {
 					Enabled:            true,
 					AllowedBidderCodes: []string{"groupm"},
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index 21ffa298..71d34334 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/mxmCherry/openrtb/v16/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
-	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -64,7 +63,7 @@ func TestAllValidBids(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 	assert.Len(t, seatBids, 1)
 	assert.Len(t, seatBids[0].bids, 4)
 	assert.Len(t, errs, 0)
@@ -134,7 +133,7 @@ func TestAllBadBids(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 	assert.Len(t, seatBids, 1)
 	assert.Len(t, seatBids[0].bids, 0)
 	assert.Len(t, errs, 7)
@@ -215,7 +214,7 @@ func TestMixedBids(t *testing.T) {
 		addCallSignHeader:   false,
 		bidAdjustments:      bidAdjustments,
 	}
-	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+	seatBids, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 	assert.Len(t, seatBids, 1)
 	assert.Len(t, seatBids[0].bids, 3)
 	assert.Len(t, errs, 5)
@@ -344,7 +343,7 @@ func TestCurrencyBids(t *testing.T) {
 			addCallSignHeader:   false,
 			bidAdjustments:      bidAdjustments,
 		}
-		seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, config.AlternateBidderCodes{})
+		seatBids, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{})
 		assert.Len(t, seatBids, 1)
 		assert.Len(t, seatBids[0].bids, expectedValidBids)
 		assert.Len(t, errs, expectedErrs)
@@ -356,6 +355,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 config.AlternateBidderCodes) ([]*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) ([]*pbsOrtbSeatBid, []error) {
 	return b.bidResponse, b.errorResponse
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 866e4d77..3bf42333 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -56,7 +56,7 @@ func TestNewExchange(t *testing.T) {
 		},
 	}
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -80,7 +80,7 @@ func TestNewExchange(t *testing.T) {
 	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, tcf2ConfigBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
 	for _, bidderName := range knownAdapters {
 		if _, ok := e.adapterMap[bidderName]; !ok {
-			if biddersInfo[strings.ToLower(string(bidderName))].IsEnabled() {
+			if biddersInfo[string(bidderName)].IsEnabled() {
 				t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName)
 			}
 		}
@@ -1239,7 +1239,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
 	defer server.Close()
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1602,7 +1602,7 @@ func TestBidResponseCurrency(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
 	defer server.Close()
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1846,7 +1846,7 @@ func TestRaceIntegration(t *testing.T) {
 
 	cfg := &config.Configuration{}
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -1955,7 +1955,7 @@ func TestPanicRecovery(t *testing.T) {
 		},
 	}
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -2024,7 +2024,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 
 	cfg := &config.Configuration{}
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -4083,7 +4083,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) {
 
 	cfg := &config.Configuration{}
 
-	biddersInfo, err := config.LoadBidderInfo("../static/bidder-info")
+	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -4195,6 +4195,185 @@ func TestCallSignHeader(t *testing.T) {
 
 }
 
+/*
+	TestOverrideConfigAlternateBidderCodesWithRequestValues makes sure that the correct alternabiddercodes list is forwarded to the adapters and only the approved bids are returned in auction response.
+
+	1. request.ext.prebid.alternatebiddercodes has priority over the content of config.Account.Alternatebiddercodes.
+
+	2. request is updated with config.Account.Alternatebiddercodes values if request.ext.prebid.alternatebiddercodes is empty or not specified.
+
+	3. request.ext.prebid.alternatebiddercodes is given priority over config.Account.Alternatebiddercodes if both are specified.
+*/
+func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) {
+	type testIn struct {
+		config     config.Configuration
+		requestExt json.RawMessage
+	}
+	type testResults struct {
+		expectedSeats []string
+	}
+
+	testCases := []struct {
+		desc     string
+		in       testIn
+		expected testResults
+	}{
+		{
+			desc: "alternatebiddercode defined neither in config nor in the request",
+			in: testIn{
+				config: config.Configuration{},
+			},
+			expected: testResults{
+				expectedSeats: []string{"pubmatic"},
+			},
+		},
+		{
+			desc: "alternatebiddercode defined in config and not in request",
+			in: testIn{
+				config: config.Configuration{
+					AccountDefaults: config.Account{
+						AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{
+							Enabled: true,
+							Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+								"pubmatic": {
+									Enabled:            true,
+									AllowedBidderCodes: []string{"groupm"},
+								},
+							},
+						},
+					},
+				},
+				requestExt: json.RawMessage(`{}`),
+			},
+			expected: testResults{
+				expectedSeats: []string{"pubmatic", "groupm"},
+			},
+		},
+		{
+			desc: "alternatebiddercode defined in request and not in config",
+			in: testIn{
+				requestExt: json.RawMessage(`{"prebid": {"alternatebiddercodes": {"enabled": true, "bidders": {"pubmatic": {"enabled": true, "allowedbiddercodes": ["appnexus"]}}}}}`),
+			},
+			expected: testResults{
+				expectedSeats: []string{"pubmatic", "appnexus"},
+			},
+		},
+		{
+			desc: "alternatebiddercode defined in both config and in request",
+			in: testIn{
+				config: config.Configuration{
+					AccountDefaults: config.Account{
+						AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{
+							Enabled: true,
+							Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+								"pubmatic": {
+									Enabled:            true,
+									AllowedBidderCodes: []string{"groupm"},
+								},
+							},
+						},
+					},
+				},
+				requestExt: json.RawMessage(`{"prebid": {"alternatebiddercodes": {"enabled": true, "bidders": {"pubmatic": {"enabled": true, "allowedbiddercodes": ["ix"]}}}}}`),
+			},
+			expected: testResults{
+				expectedSeats: []string{"pubmatic", "ix"},
+			},
+		},
+	}
+
+	// Init an exchange to run an auction from
+	noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
+	mockPubMaticBidService := httptest.NewServer(http.HandlerFunc(noBidServer))
+	defer mockPubMaticBidService.Close()
+
+	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
+	if error != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", error)
+	}
+
+	mockBidderRequestResponse := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     mockPubMaticBidService.URL,
+			Body:    []byte("{\"key\":\"val\"}"),
+			Headers: http.Header{},
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{
+				{Bid: &openrtb2.Bid{ID: "1"}, Seat: ""},
+				{Bid: &openrtb2.Bid{ID: "2"}, Seat: "pubmatic"},
+				{Bid: &openrtb2.Bid{ID: "3"}, Seat: "appnexus"},
+				{Bid: &openrtb2.Bid{ID: "4"}, Seat: "groupm"},
+				{Bid: &openrtb2.Bid{ID: "5"}, Seat: "ix"},
+			},
+			Currency: "USD",
+		},
+	}
+
+	e := new(exchange)
+	e.cache = &wellBehavedCache{}
+	e.me = &metricsConf.NilMetricsEngine{}
+	e.gdprPermsBuilder = fakePermissionsBuilder{
+		permissions: &permissionsMock{
+			allowAllBidders: true,
+		},
+	}.Builder
+	e.tcf2ConfigBuilder = fakeTCF2ConfigBuilder{
+		cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
+	}.Builder
+	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	e.categoriesFetcher = categoriesFetcher
+	e.bidIDGenerator = &mockBidIDGenerator{false, false}
+
+	// Define mock incoming bid requeset
+	mockBidRequest := &openrtb2.BidRequest{
+		ID: "some-request-id",
+		Imp: []openrtb2.Imp{{
+			ID:     "some-impression-id",
+			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
+			Ext:    json.RawMessage(`{"pubmatic": {"publisherId": 1}}`),
+		}},
+		Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
+	}
+
+	// Run tests
+	for _, test := range testCases {
+		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
+			openrtb_ext.BidderPubmatic: AdaptBidder(mockBidderRequestResponse, mockPubMaticBidService.Client(), &test.in.config, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""),
+		}
+
+		mockBidRequest.Ext = test.in.requestExt
+
+		auctionRequest := AuctionRequest{
+			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
+			Account:           test.in.config.AccountDefaults,
+			UserSyncs:         &emptyUsersync{},
+		}
+
+		// Run test
+		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
+
+		// Assertions
+		assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
+		assert.NotNil(t, outBidResponse)
+
+		// So 2 seatBids are expected as,
+		// the default "" and "pubmatic" bids will be in one seat and the extra-bids "groupm"/"appnexus"/"ix" in another seat.
+		assert.Len(t, outBidResponse.SeatBid, len(test.expected.expectedSeats), "%s. seatbid count miss-match\n", test.desc)
+
+		for i, seatBid := range outBidResponse.SeatBid {
+			assert.Contains(t, test.expected.expectedSeats, seatBid.Seat, "%s. unexpected seatbid\n", test.desc)
+
+			if seatBid.Seat == string(openrtb_ext.BidderPubmatic) {
+				assert.Len(t, outBidResponse.SeatBid[i].Bid, 2, "%s. unexpected bid count\n", test.desc)
+			} else {
+				assert.Len(t, outBidResponse.SeatBid[i].Bid, 1, "%s. unexpected bid count\n", test.desc)
+			}
+		}
+	}
+}
+
 type MockSigner struct {
 	data string
 }
@@ -4286,7 +4465,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 config.AlternateBidderCodes) (seatBids []*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) (seatBids []*pbsOrtbSeatBid, errs []error) {
 	if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok {
 		if expectedRequest != nil {
 			if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) {
@@ -4343,7 +4522,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 config.AlternateBidderCodes) (seatBid []*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) (seatBid []*pbsOrtbSeatBid, errs []error) {
 	b.req = bidderRequest.BidRequest
 	return []*pbsOrtbSeatBid{{}}, nil
 }
@@ -4449,7 +4628,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 config.AlternateBidderCodes) (posb []*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) (posb []*pbsOrtbSeatBid, errs []error) {
 	panic("Panic! Panic! The world is ending!")
 }
 
diff --git a/exchange/exchangetest/extra-bids.json b/exchange/exchangetest/extra-bids.json
index 5dc3da15..84a4cffd 100644
--- a/exchange/exchangetest/extra-bids.json
+++ b/exchange/exchangetest/extra-bids.json
@@ -40,6 +40,15 @@
           "targeting": {
             "includewinners": true,
             "includebidderkeys": true
+          },
+          "alternatebiddercodes": {
+            "enabled": true,
+            "bidders": {
+              "pubmatic": {
+                "enabled": true,
+                "allowedbiddercodes": ["groupm"]
+              }
+            }
           }
         }
       }
@@ -47,6 +56,51 @@
   },
   "outgoingRequests": {
     "pubmatic": {
+      "expectRequest": {
+        "ortbRequest": {
+          "id": "some-request-id",
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": ["video/mp4"]
+              },
+              "ext": {
+                "bidder": {
+                  "publisherId": 5890
+                }
+              }
+            },
+            {
+              "id": "imp-id-2",
+              "video": {
+                "mimes": ["video/mp4"]
+              },
+              "ext": {
+                "bidder": {
+                  "publisherId": 5890
+                }
+              }
+            }
+          ],
+          "ext": {
+            "prebid": {
+              "alternatebiddercodes": {
+                "enabled": true,
+                "bidders": {
+                  "pubmatic": {
+                    "enabled": true,
+                    "allowedbiddercodes": ["groupm"]
+                  }
+                }
+              }
+            }
+          }
+        }
+      },
       "mockResponse": {
         "pbsSeatBids": [{
           "pbsBids": [
@@ -117,6 +171,46 @@
       }
     },
     "appnexus": {
+      "expectRequest": {
+        "ortbRequest": {
+          "id": "some-request-id",
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": ["video/mp4"]
+              },
+              "ext": {
+                "bidder": {
+                  "placementId": 1
+                }
+              }
+            },
+            {
+              "id": "imp-id-2",
+              "video": {
+                "mimes": ["video/mp4"]
+              },
+              "ext": {
+                "bidder": {
+                  "placementId": 1
+                }
+              }
+            }
+          ],
+          "ext": {
+            "prebid": {
+              "alternatebiddercodes": {
+                "enabled": true,
+                "bidders": null
+              }
+            }
+          }
+        }
+      },
       "mockResponse": {
         "pbsSeatBids": [{
           "pbsBids": [
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 4ff2aa0a..ae60d42d 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"sort"
 	"testing"
 
 	"github.com/mxmCherry/openrtb/v16/openrtb2"
@@ -2419,8 +2420,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
 			privacyConfig,
 			gdprPermissionsBuilder,
 			tcf2ConfigBuilder,
-			nil,
-		)
+			nil)
 
 		// extract bidder name from each request in the results
 		bidders := []openrtb_ext.BidderName{}
@@ -2445,46 +2445,95 @@ func TestBuildRequestExtForBidder(t *testing.T) {
 	bidderParams := json.RawMessage(`"bar"`)
 
 	testCases := []struct {
-		description  string
-		requestExt   json.RawMessage
-		bidderParams map[string]json.RawMessage
-		expectedJson json.RawMessage
+		description          string
+		requestExt           json.RawMessage
+		bidderParams         map[string]json.RawMessage
+		alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes
+		expectedJson         json.RawMessage
 	}{
 		{
-			description:  "Nil",
-			bidderParams: nil,
-			requestExt:   nil,
-			expectedJson: json.RawMessage(``),
+			description:          "Nil",
+			bidderParams:         nil,
+			requestExt:           nil,
+			alternateBidderCodes: nil,
+			expectedJson:         json.RawMessage(``),
+		},
+		{
+			description:          "Empty",
+			bidderParams:         nil,
+			alternateBidderCodes: nil,
+			requestExt:           json.RawMessage(`{}`),
+			expectedJson:         json.RawMessage(`{"prebid":{}}`),
+		},
+		{
+			description:          "Prebid - Allowed Fields Only",
+			bidderParams:         nil,
+			alternateBidderCodes: nil,
+			requestExt:           json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson:         json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+		},
+		{
+			description:          "Prebid - Allowed Fields + Bidder Params",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: nil,
+			requestExt:           json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson:         json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
+		},
+		{
+			description:          "Other",
+			bidderParams:         nil,
+			alternateBidderCodes: nil,
+			requestExt:           json.RawMessage(`{"other":"foo"}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{}}`),
+		},
+		{
+			description:          "Prebid + Other + Bider Params",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: nil,
+			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Empty",
-			bidderParams: nil,
-			requestExt:   json.RawMessage(`{}`),
-			expectedJson: json.RawMessage(`{"prebid":{}}`),
+			description:          "Prebid + AlternateBidderCodes in pbs config (default explicitly defined)",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{},
+			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null},"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid - Allowed Fields Only",
-			bidderParams: nil,
-			requestExt:   json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
-			expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			description:          "Prebid + AlternateBidderCodes in pbs config",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}},
+			requestExt:           json.RawMessage(`{"other":"foo"}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid - Allowed Fields + Bidder Params",
-			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
-			requestExt:   json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
-			expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
+			description:          "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"bar": {Enabled: true, AllowedBidderCodes: []string{"*"}}}},
+			requestExt:           json.RawMessage(`{"other":"foo"}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Other",
-			bidderParams: nil,
-			requestExt:   json.RawMessage(`{"other":"foo"}`),
-			expectedJson: json.RawMessage(`{"other":"foo","prebid":{}}`),
+			description:          "Prebid + AlternateBidderCodes in request",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{},
+			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid + Other + Bider Params",
-			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
-			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
-			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
+			description:          "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{},
+			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`),
+		},
+		{
+			description:          "Prebid + AlternateBidderCodes in both pbs config and in the request",
+			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
+			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}},
+			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`),
 		},
 	}
 
@@ -2497,7 +2546,7 @@ func TestBuildRequestExtForBidder(t *testing.T) {
 			}
 		}
 
-		actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams)
+		actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams, test.alternateBidderCodes)
 		if len(test.expectedJson) > 0 {
 			assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json")
 		} else {
@@ -2509,26 +2558,28 @@ func TestBuildRequestExtForBidder(t *testing.T) {
 
 func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) {
 	var (
-		bidder           = "foo"
-		requestExt       = json.RawMessage(`{}`)
-		requestExtParsed *openrtb_ext.ExtRequest
-		bidderParams     map[string]json.RawMessage
+		bidder               = "foo"
+		requestExt           = json.RawMessage(`{}`)
+		requestExtParsed     *openrtb_ext.ExtRequest
+		bidderParams         map[string]json.RawMessage
+		alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes
 	)
 
-	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams)
+	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes)
 	assert.Equal(t, json.RawMessage(``), actualJson)
 	assert.NoError(t, actualErr)
 }
 
 func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) {
 	var (
-		bidder           = "foo"
-		requestExt       = json.RawMessage(`malformed`)
-		requestExtParsed = &openrtb_ext.ExtRequest{}
-		bidderParams     map[string]json.RawMessage
+		bidder               = "foo"
+		requestExt           = json.RawMessage(`malformed`)
+		requestExtParsed     = &openrtb_ext.ExtRequest{}
+		bidderParams         map[string]json.RawMessage
+		alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes
 	)
 
-	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams)
+	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes)
 	assert.Equal(t, json.RawMessage(nil), actualJson)
 	assert.EqualError(t, actualErr, "invalid character 'm' looking for beginning of value")
 }
@@ -3272,3 +3323,189 @@ func TestBuildExtData(t *testing.T) {
 		assert.JSONEq(t, test.expectedRes, string(actualRes), "Incorrect result data")
 	}
 }
+
+func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) {
+	testCases := []struct {
+		desc      string
+		inExt     json.RawMessage
+		inCfgABC  *openrtb_ext.ExtAlternateBidderCodes
+		wantExt   []json.RawMessage
+		wantError bool
+	}{
+		{
+			desc:     "Nil request ext, default account alternatebiddercodes config (nil)",
+			inExt:    nil,
+			inCfgABC: nil,
+			wantExt: []json.RawMessage{
+				json.RawMessage(""),
+				json.RawMessage(""),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "Nil request ext, default account alternatebiddercodes config (explicity defined)",
+			inExt:    nil,
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "request ext, default account alternatebiddercodes config (explicity defined)",
+			inExt:    json.RawMessage(`{"prebid":{}}`),
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:  "Nil request ext, account alternatebiddercodes config disabled with biddercodes defined",
+			inExt: nil,
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{
+				Enabled: false,
+				Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+					"pubmatic": {Enabled: true},
+				},
+			},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":null}}}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:  "Nil request ext, account alternatebiddercodes config disabled with biddercodes defined (not participant bidder)",
+			inExt: nil,
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{
+				Enabled: false,
+				Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+					"ix": {Enabled: true},
+				},
+			},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "Nil request ext, alternatebiddercodes config enabled but bidder not present",
+			inExt:    nil,
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "request ext with default alternatebiddercodes values (nil)",
+			inExt:    json.RawMessage(`{"prebid":{}}`),
+			inCfgABC: nil,
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{}}`),
+				json.RawMessage(`{"prebid":{}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "request ext w/o alternatebiddercodes",
+			inExt:    json.RawMessage(`{"prebid":{}}`),
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "request ext having alternatebiddercodes for only one bidder",
+			inExt:    json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`),
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "request ext having alternatebiddercodes for multiple bidder",
+			inExt:    json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]},"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:     "request ext having alternatebiddercodes for multiple bidder (config alternatebiddercodes not defined)",
+			inExt:    json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]},"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`),
+			},
+			wantError: false,
+		},
+		{
+			desc:  "Nil request ext, alternatebiddercodes config enabled with bidder code for only one bidder",
+			inExt: nil,
+			inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{
+				Enabled: true,
+				Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
+					"pubmatic": {
+						Enabled:            true,
+						AllowedBidderCodes: []string{"groupm"},
+					},
+				},
+			},
+			wantExt: []json.RawMessage{
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`),
+				json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`),
+			},
+			wantError: false,
+		},
+	}
+
+	for _, test := range testCases {
+		req := newBidRequestWithBidderParams(t)
+		req.Ext = nil
+		var extRequest *openrtb_ext.ExtRequest
+		if test.inExt != nil {
+			req.Ext = test.inExt
+			unmarshaledExt, err := extractBidRequestExt(req)
+			assert.NoErrorf(t, err, test.desc+":Error unmarshaling inExt")
+			extRequest = unmarshaledExt
+		}
+
+		auctionReq := AuctionRequest{
+			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req},
+			UserSyncs:         &emptyUsersync{},
+			Account:           config.Account{AlternateBidderCodes: test.inCfgABC},
+		}
+		gdprPermissionsBuilder := fakePermissionsBuilder{
+			permissions: &permissionsMock{
+				allowAllBidders: true,
+			},
+		}.Builder
+		tcf2ConfigBuilder := fakeTCF2ConfigBuilder{
+			cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
+		}.Builder
+		bidderToSyncerKey := map[string]string{}
+		metrics := metrics.MetricsEngineMock{}
+
+		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, config.Privacy{}, gdprPermissionsBuilder, tcf2ConfigBuilder, nil)
+		assert.Equal(t, test.wantError, len(errs) != 0, test.desc)
+		sort.Slice(bidderRequests, func(i, j int) bool {
+			return bidderRequests[i].BidderCoreName < bidderRequests[j].BidderCoreName
+		})
+		for i, wantBidderRequest := range test.wantExt {
+			assert.Equal(t, wantBidderRequest, bidderRequests[i].BidRequest.Ext, test.desc+" : "+string(bidderRequests[i].BidderCoreName)+"\n\t\tGotRequestExt : "+string(bidderRequests[i].BidRequest.Ext))
+		}
+	}
+}
diff --git a/openrtb_ext/alternatebiddercodes_test.go b/openrtb_ext/alternatebiddercodes_test.go
new file mode 100644
index 00000000..438aebad
--- /dev/null
+++ b/openrtb_ext/alternatebiddercodes_test.go
@@ -0,0 +1,180 @@
+package openrtb_ext
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAlternateBidderCodes_IsValidBidderCode(t *testing.T) {
+	type fields struct {
+		Enabled bool
+		Bidders map[string]ExtAdapterAlternateBidderCodes
+	}
+	type args struct {
+		bidder          string
+		alternateBidder string
+	}
+	tests := []struct {
+		name        string
+		fields      fields
+		args        args
+		wantIsValid bool
+		wantErr     error
+	}{
+		{
+			name:        "alternateBidder is not set/blank (default non-extra bid case)",
+			wantIsValid: true,
+		},
+		{
+			name: "alternateBidder and bidder are same (default non-extra bid case with seat's alternateBidder explicitly set)",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "pubmatic",
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "account.alternatebiddercodes config not defined (default, reject bid)",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "account.alternatebiddercodes config enabled but adapter config not defined",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields:      fields{Enabled: true},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "account.alternatebiddercodes config enabled but adapter config is not available",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"appnexus": {},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes not defined for adapter "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "account.alternatebiddercodes config enabled but adapter config is disabled",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"pubmatic": {Enabled: false},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`alternateBidderCodes disabled for "pubmatic", rejecting bids for "groupm"`),
+		},
+		{
+			name: "account.alternatebiddercodes and adapter config enabled but adapter config does not have allowedBidderCodes defined",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"pubmatic": {Enabled: true},
+				},
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "allowedBidderCodes is *",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"pubmatic": {
+						Enabled:            true,
+						AllowedBidderCodes: []string{"*"},
+					},
+				},
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "allowedBidderCodes is in the list",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"pubmatic": {
+						Enabled:            true,
+						AllowedBidderCodes: []string{"groupm"},
+					},
+				},
+			},
+			wantIsValid: true,
+		},
+		{
+			name: "allowedBidderCodes is not in the list",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"pubmatic": {
+						Enabled:            true,
+						AllowedBidderCodes: []string{"xyz"},
+					},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`),
+		},
+		{
+			name: "account.alternatebiddercodes and adapter config enabled but adapter config has allowedBidderCodes list empty",
+			args: args{
+				bidder:          "pubmatic",
+				alternateBidder: "groupm",
+			},
+			fields: fields{
+				Enabled: true,
+				Bidders: map[string]ExtAdapterAlternateBidderCodes{
+					"pubmatic": {Enabled: true, AllowedBidderCodes: []string{}},
+				},
+			},
+			wantIsValid: false,
+			wantErr:     errors.New(`invalid biddercode "groupm" sent by adapter "pubmatic"`),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &ExtAlternateBidderCodes{
+				Enabled: tt.fields.Enabled,
+				Bidders: tt.fields.Bidders,
+			}
+			gotIsValid, gotErr := a.IsValidBidderCode(tt.args.bidder, tt.args.alternateBidder)
+			assert.Equal(t, tt.wantIsValid, gotIsValid)
+			assert.Equal(t, tt.wantErr, gotErr)
+		})
+	}
+}