Repository: prebid/prebid-server
Pull request #2535 (2023-01-19 23:52:00)

openx: add FLEDGE support
Add FLEDGE support in openx adapter, following up on #2445 


Patch:
diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml
new file mode 100644
index 00000000..1c9c0a7e
--- /dev/null
+++ b/.github/workflows/cross-repo-issue.yml
@@ -0,0 +1,28 @@
+name: Cross-repo Issue Creation
+
+on:
+  pull_request:
+    types: [closed]
+    branches:
+      - "master"
+
+jobs:
+  cross-repo:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Generate token
+        id: generate_token
+        uses: tibdex/github-app-token@v1
+        with:
+          app_id: ${{ secrets.XREPO_APP_ID }}
+          private_key: ${{ secrets.XREPO_PEM }}
+      - name: create issue in other repo
+        if: "!contains(github.event.pull_request.labels.*.name, 'do not port') && github.event.pull_request.merged"
+        env:
+          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+        run: |
+          echo -e "A PR was merged over on PBS-Go\n\n- [https://github.com/prebid/prebid-server/pull/${{github.event.number}}](https://github.com/prebid/prebid-server/pull/${{github.event.number}})\n- timestamp: ${{ github.event.pull_request.merged_at}}" > msg
+          export msg=$(cat msg)
+          gh issue create --repo prebid/prebid-server-java --title "Port PR from PBS-Go: ${{ github.event.pull_request.title }}" \
+              --body "$msg" \
+              --label auto
diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go
index 3b01dab5..084ffc22 100644
--- a/adapters/adnuntius/adnuntius.go
+++ b/adapters/adnuntius/adnuntius.go
@@ -76,8 +76,9 @@ const minutesInHour = 60
 // Builder builds a new instance of the Adnuntius adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &adapter{
-		time:     &timeutil.RealTime{},
-		endpoint: config.Endpoint,
+		time:      &timeutil.RealTime{},
+		endpoint:  config.Endpoint,
+		extraInfo: config.ExtraAdapterInfo,
 	}
 
 	return bidder, nil
diff --git a/adapters/brave/brave.go b/adapters/brave/brave.go
new file mode 100755
index 00000000..627b6547
--- /dev/null
+++ b/adapters/brave/brave.go
@@ -0,0 +1,157 @@
+package brave
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint *template.Template
+}
+
+// Builder builds a new instance of the Brave adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	uri, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: uri,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var braveExt *openrtb_ext.ExtImpBrave
+	var err error
+
+	braveExt, err = a.getImpressionExt(&request.Imp[0])
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	request.Imp[0].Ext = nil
+
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	url, err := a.buildEndpointURL(braveExt)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	return []*adapters.RequestData{{
+		Method:  http.MethodPost,
+		Body:    reqJSON,
+		Uri:     url,
+		Headers: headers,
+	}}, nil
+}
+
+func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBrave, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "ext.bidder not provided",
+		}
+	}
+	var braveExt openrtb_ext.ExtImpBrave
+	if err := json.Unmarshal(bidderExt.Bidder, &braveExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "ext.bidder not provided",
+		}
+	}
+	return &braveExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpBrave) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{PublisherID: params.PlacementID}
+	return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+func (a *adapter) MakeBids(
+	receivedRequest *openrtb2.BidRequest,
+	bidderRequest *adapters.RequestData,
+	bidderResponse *adapters.ResponseData,
+) (
+	*adapters.BidderResponse,
+	[]error,
+) {
+
+	if bidderResponse.StatusCode == http.StatusNoContent {
+		return nil, []error{&errortypes.BadInput{Message: "No bid"}}
+	}
+
+	if bidderResponse.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
+		}}
+	}
+
+	if bidderResponse.StatusCode == http.StatusServiceUnavailable {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Service Unavailable. Status Code: [ %d ] ", bidderResponse.StatusCode),
+		}}
+	}
+
+	if bidderResponse.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderResponse.StatusCode),
+		}}
+	}
+
+	var bidResponse openrtb2.BidResponse
+	if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Bad Server Response",
+		}}
+	}
+
+	if len(bidResponse.SeatBid) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Empty SeatBid array",
+		}}
+	}
+
+	bidResponseFinal := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid))
+	sb := bidResponse.SeatBid[0]
+
+	for _, bid := range sb.Bid {
+		bidResponseFinal.Bids = append(bidResponseFinal.Bids, &adapters.TypedBid{
+			Bid:     &bid,
+			BidType: getMediaTypeForImp(bid.ImpID, receivedRequest.Imp),
+		})
+	}
+	return bidResponseFinal, nil
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impId {
+			if imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			} else if imp.Native != nil {
+				mediaType = openrtb_ext.BidTypeNative
+			}
+			return mediaType
+		}
+	}
+	return mediaType
+}
diff --git a/adapters/eplanning/eplanning.go b/adapters/eplanning/eplanning.go
index 061627a8..9889b2a8 100644
--- a/adapters/eplanning/eplanning.go
+++ b/adapters/eplanning/eplanning.go
@@ -26,6 +26,11 @@ const defaultPageURL = "FILE"
 const sec = "ROS"
 const dfpClientID = "1"
 const requestTargetInventory = "1"
+const vastInstream = 1
+const vastOutstream = 2
+const vastVersionDefault = "3"
+const vastDefaultSize = "640x480"
+const impTypeBanner = 0
 
 var priorityOrderForMobileSizesAsc = []string{"1x1", "300x50", "320x50", "300x250"}
 var priorityOrderForDesktopSizesAsc = []string{"1x1", "970x90", "970x250", "160x600", "300x600", "728x90", "300x250"}
@@ -72,10 +77,12 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb2.BidRequest, reqI
 	totalRequests := 0
 	clientID := ""
 	isMobile := isMobileDevice(request)
+	impType := getImpTypeRequest(request, totalImps)
+	index_vast := 0
 
 	for i := 0; i < totalImps; i++ {
 		imp := request.Imp[i]
-		extImp, err := verifyImp(&imp, isMobile)
+		extImp, err := verifyImp(&imp, isMobile, impType)
 		if err != nil {
 			errors = append(errors, err)
 			continue
@@ -88,7 +95,14 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb2.BidRequest, reqI
 		totalRequests++
 		// Save valid imp
 		name := cleanName(extImp.AdUnitCode)
-		spacesStrings = append(spacesStrings, name+":"+extImp.SizeString)
+		if imp.Video != nil {
+			name = getNameVideo(extImp.SizeString, index_vast)
+			spacesStrings = append(spacesStrings, name+":"+extImp.SizeString+";1")
+			index_vast++
+		} else {
+			spacesStrings = append(spacesStrings, name+":"+extImp.SizeString)
+		}
+
 	}
 
 	if totalRequests == 0 {
@@ -177,6 +191,11 @@ func (adapter *EPlanningAdapter) MakeRequests(request *openrtb2.BidRequest, reqI
 		query.Set("app", requestTargetInventory)
 	}
 
+	if impType > 0 {
+		query.Set("vctx", strconv.Itoa(impType))
+		query.Set("vv", vastVersionDefault)
+	}
+
 	uriObj.RawQuery = query.Encode()
 	uri := uriObj.String()
 
@@ -196,6 +215,23 @@ func isMobileDevice(request *openrtb2.BidRequest) bool {
 	return request.Device != nil && (request.Device.DeviceType == adcom1.DeviceMobile || request.Device.DeviceType == adcom1.DevicePhone || request.Device.DeviceType == adcom1.DeviceTablet)
 }
 
+func getImpTypeRequest(request *openrtb2.BidRequest, totalImps int) int {
+
+	impType := impTypeBanner
+	for i := 0; i < totalImps; i++ {
+		imp := request.Imp[i]
+		if imp.Video != nil {
+			if imp.Video.Placement == vastInstream {
+				impType = vastInstream
+			} else if impType == impTypeBanner {
+				impType = vastOutstream
+			}
+		}
+	}
+
+	return impType
+
+}
 func cleanName(name string) string {
 	for _, step := range cleanNameSteps {
 		name = step.expression.ReplaceAllString(name, step.replacementString)
@@ -203,7 +239,7 @@ func cleanName(name string) string {
 	return name
 }
 
-func verifyImp(imp *openrtb2.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning, error) {
+func verifyImp(imp *openrtb2.Imp, isMobile bool, impType int) (*openrtb_ext.ExtImpEPlanning, error) {
 	var bidderExt adapters.ExtImpBidder
 
 	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
@@ -212,6 +248,24 @@ func verifyImp(imp *openrtb2.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning,
 		}
 	}
 
+	if impType > impTypeBanner {
+		if impType == vastInstream {
+			// In-stream
+			if imp.Video == nil || imp.Video.Placement != vastInstream {
+				return nil, &errortypes.BadInput{
+					Message: fmt.Sprintf("Ignoring imp id=%s, auction instream and imp no instream", imp.ID),
+				}
+			}
+		} else {
+			//Out-stream
+			if imp.Video == nil || imp.Video.Placement == vastInstream {
+				return nil, &errortypes.BadInput{
+					Message: fmt.Sprintf("Ignoring imp id=%s, auction outstream and imp no outstream", imp.ID),
+				}
+			}
+		}
+	}
+
 	impExt := openrtb_ext.ExtImpEPlanning{}
 	err := json.Unmarshal(bidderExt.Bidder, &impExt)
 	if err != nil {
@@ -229,7 +283,11 @@ func verifyImp(imp *openrtb2.Imp, isMobile bool) (*openrtb_ext.ExtImpEPlanning,
 	width, height := getSizeFromImp(imp, isMobile)
 
 	if width == 0 && height == 0 {
-		impExt.SizeString = nullSize
+		if imp.Video != nil {
+			impExt.SizeString = vastDefaultSize
+		} else {
+			impExt.SizeString = nullSize
+		}
 	} else {
 		impExt.SizeString = fmt.Sprintf("%dx%d", width, height)
 	}
@@ -251,23 +309,30 @@ func searchSizePriority(hashedFormats map[string]int, format []openrtb2.Format,
 }
 
 func getSizeFromImp(imp *openrtb2.Imp, isMobile bool) (int64, int64) {
-	if imp.Banner.W != nil && imp.Banner.H != nil {
-		return *imp.Banner.W, *imp.Banner.H
+
+	if imp.Video != nil && imp.Video.W > 0 && imp.Video.H > 0 {
+		return imp.Video.W, imp.Video.H
 	}
 
-	if imp.Banner.Format != nil {
-		hashedFormats := make(map[string]int, len(imp.Banner.Format))
+	if imp.Banner != nil {
+		if imp.Banner.W != nil && imp.Banner.H != nil {
+			return *imp.Banner.W, *imp.Banner.H
+		}
+
+		if imp.Banner.Format != nil {
+			hashedFormats := make(map[string]int, len(imp.Banner.Format))
 
-		for i, format := range imp.Banner.Format {
-			if format.W != 0 && format.H != 0 {
-				hashedFormats[fmt.Sprintf("%dx%d", format.W, format.H)] = i
+			for i, format := range imp.Banner.Format {
+				if format.W != 0 && format.H != 0 {
+					hashedFormats[fmt.Sprintf("%dx%d", format.W, format.H)] = i
+				}
 			}
-		}
 
-		if isMobile {
-			return searchSizePriority(hashedFormats, imp.Banner.Format, priorityOrderForMobileSizesAsc)
-		} else {
-			return searchSizePriority(hashedFormats, imp.Banner.Format, priorityOrderForDesktopSizesAsc)
+			if isMobile {
+				return searchSizePriority(hashedFormats, imp.Banner.Format, priorityOrderForMobileSizesAsc)
+			} else {
+				return searchSizePriority(hashedFormats, imp.Banner.Format, priorityOrderForDesktopSizesAsc)
+			}
 		}
 	}
 
@@ -305,18 +370,24 @@ func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb2.BidRequest,
 	}
 
 	isMobile := isMobileDevice(internalRequest)
+	impType := getImpTypeRequest(internalRequest, len(internalRequest.Imp))
 
 	bidResponse := adapters.NewBidderResponse()
 
 	spaceNameToImpID := make(map[string]string)
 
+	index_vast := 0
 	for _, imp := range internalRequest.Imp {
-		extImp, err := verifyImp(&imp, isMobile)
+		extImp, err := verifyImp(&imp, isMobile, impType)
 		if err != nil {
 			continue
 		}
 
 		name := cleanName(extImp.AdUnitCode)
+		if imp.Video != nil {
+			name = getNameVideo(extImp.SizeString, index_vast)
+			index_vast++
+		}
 		spaceNameToImpID[name] = imp.ID
 	}
 
@@ -336,7 +407,7 @@ func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb2.BidRequest,
 
 				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 					Bid:     &bid,
-					BidType: openrtb_ext.BidTypeBanner,
+					BidType: getBidType(impType),
 				})
 			}
 		}
@@ -345,6 +416,19 @@ func (adapter *EPlanningAdapter) MakeBids(internalRequest *openrtb2.BidRequest,
 	return bidResponse, nil
 }
 
+func getBidType(impType int) openrtb_ext.BidType {
+
+	bidType := openrtb_ext.BidTypeBanner
+	if impType > 0 {
+		bidType = openrtb_ext.BidTypeVideo
+	}
+	return bidType
+}
+
+func getNameVideo(size string, index_vast int) string {
+	return "video_" + size + "_" + strconv.Itoa(index_vast)
+}
+
 // Builder builds a new instance of the EPlanning adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &EPlanningAdapter{
diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go
index 5d5cad1f..021d033f 100644
--- a/adapters/mobilefuse/mobilefuse.go
+++ b/adapters/mobilefuse/mobilefuse.go
@@ -182,7 +182,7 @@ func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest,
 	var validImps []openrtb2.Imp
 
 	for _, imp := range bidRequest.Imp {
-		if imp.Banner != nil || imp.Video != nil {
+		if imp.Banner != nil || imp.Video != nil || imp.Native != nil {
 			imp.TagID = strconv.Itoa(ext.PlacementId)
 			imp.Ext = nil
 			validImps = append(validImps, imp)
@@ -198,9 +198,12 @@ func (adapter *MobileFuseAdapter) getBidType(bid openrtb2.Bid) openrtb_ext.BidTy
 	if bid.Ext != nil {
 		var bidExt BidExt
 		err := json.Unmarshal(bid.Ext, &bidExt)
-
-		if err == nil && bidExt.Mf.MediaType == "video" {
-			return openrtb_ext.BidTypeVideo
+		if err == nil {
+			if bidExt.Mf.MediaType == "video" {
+				return openrtb_ext.BidTypeVideo
+			} else if bidExt.Mf.MediaType == "native" {
+				return openrtb_ext.BidTypeNative
+			}
 		}
 	}
 
diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go
index 2240bd76..269d1c77 100644
--- a/adapters/openx/openx.go
+++ b/adapters/openx/openx.go
@@ -15,11 +15,13 @@ import (
 const hbconfig = "hb_pbs_1.0.0"
 
 type OpenxAdapter struct {
-	endpoint string
+	bidderName string
+	endpoint   string
 }
 
 type openxImpExt struct {
-	CustomParams map[string]interface{} `json:"customParams,omitempty"`
+	CustomParams       map[string]interface{}             `json:"customParams,omitempty"`
+	AuctionEnvironment openrtb_ext.AuctionEnvironmentType `json:"ae,omitempty"`
 }
 
 type openxReqExt struct {
@@ -28,6 +30,10 @@ type openxReqExt struct {
 	BidderConfig string `json:"bc"`
 }
 
+type openxRespExt struct {
+	FledgeAuctionConfigs map[string]json.RawMessage `json:"fledge_auction_configs,omitempty"`
+}
+
 func (a *OpenxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	var errs []error
 	var bannerImps []openrtb2.Imp
@@ -133,18 +139,27 @@ func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error {
 	if imp.BidFloor == 0 && openxExt.CustomFloor > 0 {
 		imp.BidFloor = openxExt.CustomFloor
 	}
-	imp.Ext = nil
+
+	impExt := openxImpExt{}
+	addImpExt := false
 
 	if openxExt.CustomParams != nil {
-		impExt := openxImpExt{
-			CustomParams: openxExt.CustomParams,
-		}
+		impExt.CustomParams = openxExt.CustomParams
+		addImpExt = true
+	}
+	if bidderExt.AuctionEnvironment != openrtb_ext.ServerSideAuction {
+		impExt.AuctionEnvironment = bidderExt.AuctionEnvironment
+		addImpExt = true
+	}
+	if addImpExt {
 		var err error
 		if imp.Ext, err = json.Marshal(impExt); err != nil {
 			return &errortypes.BadInput{
 				Message: err.Error(),
 			}
 		}
+	} else {
+		imp.Ext = nil
 	}
 
 	if imp.Video != nil {
@@ -189,6 +204,21 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRe
 		bidResponse.Currency = bidResp.Cur
 	}
 
+	if bidResp.Ext != nil {
+		var bidRespExt openxRespExt
+		if err := json.Unmarshal(bidResp.Ext, &bidRespExt); err == nil && bidRespExt.FledgeAuctionConfigs != nil {
+			bidResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.FledgeAuctionConfigs))
+			for impId, config := range bidRespExt.FledgeAuctionConfigs {
+				fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{
+					ImpId:  impId,
+					Bidder: a.bidderName,
+					Config: config,
+				}
+				bidResponse.FledgeAuctionConfigs = append(bidResponse.FledgeAuctionConfigs, fledgeAuctionConfig)
+			}
+		}
+	}
+
 	for _, sb := range bidResp.SeatBid {
 		for i := range sb.Bid {
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
@@ -220,7 +250,8 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
 // Builder builds a new instance of the Openx adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &OpenxAdapter{
-		endpoint: config.Endpoint,
+		endpoint:   config.Endpoint,
+		bidderName: string(bidderName),
 	}
 	return bidder, nil
 }
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index f808a404..5378e7a5 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -51,6 +51,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/blue"
 	"github.com/prebid/prebid-server/adapters/bmtm"
 	"github.com/prebid/prebid-server/adapters/boldwin"
+	"github.com/prebid/prebid-server/adapters/brave"
 	"github.com/prebid/prebid-server/adapters/brightroll"
 	"github.com/prebid/prebid-server/adapters/ccx"
 	"github.com/prebid/prebid-server/adapters/coinzilla"
@@ -215,6 +216,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderBlue:              blue.Builder,
 		openrtb_ext.BidderBmtm:              bmtm.Builder,
 		openrtb_ext.BidderBoldwin:           boldwin.Builder,
+		openrtb_ext.BidderBrave:             brave.Builder,
 		openrtb_ext.BidderBrightroll:        brightroll.Builder,
 		openrtb_ext.BidderCcx:               ccx.Builder,
 		openrtb_ext.BidderCoinzilla:         coinzilla.Builder,
diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go
index 242d420f..af3300b2 100644
--- a/exchange/price_granularity.go
+++ b/exchange/price_granularity.go
@@ -13,7 +13,7 @@ func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
 	bucketMax := 0.0
 	increment := 0.0
 	precision := config.Precision
-
+	bucketMin := 0.0
 	for i := 0; i < len(config.Ranges); i++ {
 		if config.Ranges[i].Max > bucketMax {
 			bucketMax = config.Ranges[i].Max
@@ -21,6 +21,7 @@ func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
 		// find what range cpm is in
 		if cpm >= config.Ranges[i].Min && cpm <= config.Ranges[i].Max {
 			increment = config.Ranges[i].Increment
+			bucketMin = config.Ranges[i].Min
 		}
 	}
 
@@ -29,13 +30,13 @@ func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
 		cpmStr = strconv.FormatFloat(bucketMax, 'f', precision, 64)
 	} else if increment > 0 {
 		// If increment exists, get cpm string value
-		cpmStr = getCpmTarget(cpm, increment, precision)
+		cpmStr = getCpmTarget(cpm, bucketMin, increment, precision)
 	}
 
 	return cpmStr
 }
 
-func getCpmTarget(cpm float64, increment float64, precision int) string {
-	roundedCPM := math.Floor(cpm/increment) * increment
+func getCpmTarget(cpm float64, bucketMin float64, increment float64, precision int) string {
+	roundedCPM := math.Floor((cpm-bucketMin)/increment)*increment + bucketMin
 	return strconv.FormatFloat(roundedCPM, 'f', precision, 64)
 }
diff --git a/go.mod b/go.mod
index 90f87507..f3e3cdab 100644
--- a/go.mod
+++ b/go.mod
@@ -30,8 +30,8 @@ require (
 	github.com/vrischmann/go-metrics-influxdb v0.1.1
 	github.com/xeipuuv/gojsonschema v1.2.0
 	github.com/yudai/gojsondiff v1.0.0
-	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
-	golang.org/x/text v0.3.7
+	golang.org/x/net v0.5.0
+	golang.org/x/text v0.6.0
 	google.golang.org/grpc v1.46.2
 	gopkg.in/evanphx/json-patch.v4 v4.12.0
 	gopkg.in/yaml.v3 v3.0.1
@@ -67,7 +67,7 @@ require (
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	github.com/yudai/pp v2.0.1+incompatible // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.4.0 // indirect
 	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
diff --git a/go.sum b/go.sum
index 30f3017b..2e795639 100644
--- a/go.sum
+++ b/go.sum
@@ -29,18 +29,12 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW
 cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
 cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
 cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
-cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
-cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
-cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
-cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
-cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
@@ -90,7 +84,6 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
 github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@@ -118,13 +111,11 @@ github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -142,7 +133,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
-github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@@ -221,7 +211,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -249,14 +238,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
-github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
-github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
-github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
-github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
 github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -266,7 +250,6 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9
 github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
 github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -295,7 +278,6 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
 github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
-github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
 github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
 github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@@ -329,11 +311,9 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
@@ -423,7 +403,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -450,7 +429,6 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
 github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -458,7 +436,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
 github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
-github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -520,12 +497,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
-go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
-go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -637,13 +610,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -661,9 +629,6 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -675,7 +640,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -755,18 +719,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -778,8 +735,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -843,8 +801,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -878,14 +834,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
 google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
 google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
-google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
-google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
-google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
-google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
-google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
-google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
-google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
-google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -959,20 +907,7 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
-google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
 google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -1005,7 +940,6 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
 google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
 google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
@@ -1052,7 +986,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1065,4 +998,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/hooks/hookanalytics/analytics.go b/hooks/hookanalytics/analytics.go
index 7907e57d..322a04b9 100644
--- a/hooks/hookanalytics/analytics.go
+++ b/hooks/hookanalytics/analytics.go
@@ -28,7 +28,7 @@ type Result struct {
 }
 
 type AppliedTo struct {
-	Bidders  []string `json:"bidders,omitempty"`
+	Bidder   string   `json:"bidder,omitempty"`
 	BidIds   []string `json:"bidids,omitempty"`
 	ImpIds   []string `json:"impids,omitempty"`
 	Request  bool     `json:"request,omitempty"`
diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go
index 1d9b4a52..20fa76d8 100644
--- a/hooks/hookexecution/execution.go
+++ b/hooks/hookexecution/execution.go
@@ -164,6 +164,9 @@ func handleHookResponses[P any](
 	return groupOutcome, payload, groupModuleCtx, nil
 }
 
+// moduleReplacer changes unwanted symbols to be in compliance with metric naming requirements
+var moduleReplacer = strings.NewReplacer(".", "_", "-", "_")
+
 // handleHookResponse is a strategy function that selects and applies
 // one of the available algorithms to handle hook response.
 func handleHookResponse[P any](
@@ -173,7 +176,7 @@ func handleHookResponse[P any](
 	metricEngine metrics.MetricsEngine,
 ) (P, HookOutcome, *RejectError) {
 	var rejectErr *RejectError
-	labels := metrics.ModuleLabels{Module: hr.HookID.ModuleCode, Stage: ctx.stage, AccountID: ctx.accountId}
+	labels := metrics.ModuleLabels{Module: moduleReplacer.Replace(hr.HookID.ModuleCode), Stage: ctx.stage, AccountID: ctx.accountId}
 	metricEngine.RecordModuleCalled(labels, hr.ExecutionTime)
 
 	hookOutcome := HookOutcome{
diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go
index 15fa7d03..a19e7368 100644
--- a/hooks/hookexecution/executor.go
+++ b/hooks/hookexecution/executor.go
@@ -215,7 +215,8 @@ func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderRe
 	executionCtx := e.newContext(stageName)
 	payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder}
 
-	outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
+	outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
+	response.Bids = payload.Bids
 	outcome.Entity = entity(bidder)
 	outcome.Stage = stageName
 
diff --git a/hooks/hookstage/rawbidderresponse_mutations.go b/hooks/hookstage/rawbidderresponse_mutations.go
new file mode 100644
index 00000000..61c0de10
--- /dev/null
+++ b/hooks/hookstage/rawbidderresponse_mutations.go
@@ -0,0 +1,43 @@
+package hookstage
+
+import (
+	"errors"
+
+	"github.com/prebid/prebid-server/adapters"
+)
+
+func (c *ChangeSet[T]) RawBidderResponse() ChangeSetRawBidderResponse[T] {
+	return ChangeSetRawBidderResponse[T]{changeSet: c}
+}
+
+type ChangeSetRawBidderResponse[T any] struct {
+	changeSet *ChangeSet[T]
+}
+
+func (c ChangeSetRawBidderResponse[T]) Bids() ChangeSetBids[T] {
+	return ChangeSetBids[T]{changeSetRawBidderResponse: c}
+}
+
+func (c ChangeSetRawBidderResponse[T]) castPayload(p T) (RawBidderResponsePayload, error) {
+	if payload, ok := any(p).(RawBidderResponsePayload); ok {
+		return payload, nil
+	}
+	return RawBidderResponsePayload{}, errors.New("failed to cast RawBidderResponsePayload")
+}
+
+type ChangeSetBids[T any] struct {
+	changeSetRawBidderResponse ChangeSetRawBidderResponse[T]
+}
+
+func (c ChangeSetBids[T]) Update(bids []*adapters.TypedBid) {
+	c.changeSetRawBidderResponse.changeSet.AddMutation(func(p T) (T, error) {
+		bidderPayload, err := c.changeSetRawBidderResponse.castPayload(p)
+		if err == nil {
+			bidderPayload.Bids = bids
+		}
+		if payload, ok := any(bidderPayload).(T); ok {
+			return payload, nil
+		}
+		return p, errors.New("failed to cast RawBidderResponsePayload")
+	}, MutationUpdate, "bids")
+}
diff --git a/modules/prebid/ortb2blocking/analytics.go b/modules/prebid/ortb2blocking/analytics.go
new file mode 100644
index 00000000..4026b672
--- /dev/null
+++ b/modules/prebid/ortb2blocking/analytics.go
@@ -0,0 +1,94 @@
+package ortb2blocking
+
+import (
+	"github.com/prebid/prebid-server/hooks/hookanalytics"
+	"github.com/prebid/prebid-server/hooks/hookstage"
+)
+
+const enforceBlockingTag = "enforce_blocking"
+
+const (
+	attributesAnalyticKey = "attributes"
+	badvAnalyticKey       = "adomain"
+	cattaxAnalyticKey     = "bcat"
+	bappAnalyticKey       = "bundle"
+	battrAnalyticKey      = "attr"
+)
+
+// ortb2blocking module has only 1 activity: `enforce_blocking` which will be used in further result processing
+func newEnforceBlockingTags() hookanalytics.Analytics {
+	return hookanalytics.Analytics{
+		Activities: []hookanalytics.Activity{
+			{
+				Name:   enforceBlockingTag,
+				Status: hookanalytics.ActivityStatusSuccess,
+			},
+		},
+	}
+}
+
+func addFailedStatusTag(result *hookstage.HookResult[hookstage.RawBidderResponsePayload]) {
+	result.AnalyticsTags.Activities[0].Status = hookanalytics.ActivityStatusError
+}
+
+func addAllowedAnalyticTag(result *hookstage.HookResult[hookstage.RawBidderResponsePayload], bidder, ImpID string) {
+	newAllowedResult := hookanalytics.Result{
+		Status: hookanalytics.ResultStatusAllow,
+		AppliedTo: hookanalytics.AppliedTo{
+			Bidder: bidder,
+			ImpIds: []string{ImpID},
+		},
+	}
+
+	result.AnalyticsTags.Activities[0].Results = append(result.AnalyticsTags.Activities[0].Results, newAllowedResult)
+}
+
+func addBlockedAnalyticTag(
+	result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
+	bidder, ImpID string,
+	failedAttributes []string,
+	data map[string]interface{},
+) {
+	values := make(map[string]interface{})
+
+	values[attributesAnalyticKey] = failedAttributes
+	for _, attribute := range [5]string{
+		"badv",
+		"bcat",
+		"cattax",
+		"bapp",
+		"battr",
+	} {
+		if _, ok := data[attribute]; ok {
+			analyticKey := getAnalyticKeyForAttribute(attribute)
+			values[analyticKey] = data[attribute]
+		}
+	}
+
+	newBlockedResult := hookanalytics.Result{
+		Status: hookanalytics.ResultStatusBlock,
+		Values: values,
+		AppliedTo: hookanalytics.AppliedTo{
+			Bidder: bidder,
+			ImpIds: []string{ImpID},
+		},
+	}
+
+	result.AnalyticsTags.Activities[0].Results = append(result.AnalyticsTags.Activities[0].Results, newBlockedResult)
+}
+
+// most of the attributes have their own representation for an analytic key
+func getAnalyticKeyForAttribute(attribute string) string {
+	switch attribute {
+	case "badv":
+		return badvAnalyticKey
+	case "cattax":
+		return cattaxAnalyticKey
+	case "bapp":
+		return bappAnalyticKey
+	case "battr":
+		return battrAnalyticKey
+	default:
+		return attribute
+	}
+}
diff --git a/modules/prebid/ortb2blocking/hook_bidderrequest.go b/modules/prebid/ortb2blocking/hook_bidderrequest.go
index 1f99cc8a..8b21e212 100644
--- a/modules/prebid/ortb2blocking/hook_bidderrequest.go
+++ b/modules/prebid/ortb2blocking/hook_bidderrequest.go
@@ -345,6 +345,10 @@ func getIds(override Override) ([]int, error) {
 	return override.Ids, nil
 }
 
+func getIsActive(override Override) (bool, error) {
+	return override.IsActive, nil
+}
+
 type mediaTypes map[string]struct{}
 
 func (m mediaTypes) String() string {
diff --git a/modules/prebid/ortb2blocking/hook_raw_bidder_response.go b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go
new file mode 100644
index 00000000..e5ebbb6f
--- /dev/null
+++ b/modules/prebid/ortb2blocking/hook_raw_bidder_response.go
@@ -0,0 +1,382 @@
+package ortb2blocking
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/prebid/openrtb/v17/adcom1"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/hooks/hookexecution"
+	"github.com/prebid/prebid-server/hooks/hookstage"
+)
+
+func handleRawBidderResponseHook(
+	cfg config,
+	payload hookstage.RawBidderResponsePayload,
+	moduleCtx hookstage.ModuleContext,
+) (result hookstage.HookResult[hookstage.RawBidderResponsePayload], err error) {
+	bidder := payload.Bidder
+	blockAttrsVal, ok := moduleCtx[bidder]
+	if !ok {
+		// if there are no blocking attributes for this bidder just pass empty blockingAttributes for further processing
+		// other values from config must still be checked
+		blockAttrsVal = blockingAttributes{}
+	}
+
+	blockAttrs, ok := blockAttrsVal.(blockingAttributes)
+	if !ok {
+		return result, hookexecution.NewFailure("could not cast blocking attributes for bidder `%s`, module context has incorrect data", payload.Bidder)
+	}
+
+	result.AnalyticsTags = newEnforceBlockingTags()
+
+	// allowedBids will store all bids that have passed the attribute check
+	allowedBids := make([]*adapters.TypedBid, 0)
+	for _, bid := range payload.Bids {
+
+		failedChecksData := make(map[string]interface{})
+		bidMediaTypes := mediaTypesFromBid(bid)
+		dealID := bid.Bid.DealID
+
+		failedChecksData, err = shouldBeBlockedDueToBadv(&result, cfg, blockAttrs, bid.Bid, bidMediaTypes, bidder, dealID, failedChecksData)
+		if err != nil {
+			addFailedStatusTag(&result)
+			return result, hookexecution.NewFailure("failed to process badv block checking: %s", err)
+		}
+
+		failedChecksData, enforceBlocks, err := shouldBeBlockedDueToBcat(&result, cfg, blockAttrs, bid.Bid, bidMediaTypes, bidder, dealID, failedChecksData)
+		if err != nil {
+			addFailedStatusTag(&result)
+			return result, hookexecution.NewFailure("failed to process bcat block checking: %s", err)
+		}
+
+		failedChecksData = shouldBeBlockedDueToCattax(blockAttrs, bid.Bid, enforceBlocks, failedChecksData)
+
+		failedChecksData, err = shouldBeBlockedDueToBapp(&result, cfg, blockAttrs, bid.Bid, bidMediaTypes, bidder, dealID, failedChecksData)
+		if err != nil {
+			addFailedStatusTag(&result)
+			return result, hookexecution.NewFailure("failed to process bapp block checking: %s", err)
+		}
+
+		failedChecksData, err = shouldBeBlockedDueToBattr(&result, cfg, blockAttrs, bid.Bid, bidMediaTypes, bidder, dealID, failedChecksData)
+		if err != nil {
+			addFailedStatusTag(&result)
+			return result, hookexecution.NewFailure("failed to process battr block checking: %s", err)
+		}
+
+		if len(failedChecksData) == 0 {
+			addAllowedAnalyticTag(&result, bidder, bid.Bid.ImpID)
+			allowedBids = append(allowedBids, bid)
+		} else {
+			failedAttributes := getFailedAttributes(failedChecksData)
+			addBlockedAnalyticTag(&result, bidder, bid.Bid.ImpID, failedAttributes, failedChecksData)
+			addDebugMessage(&result, bid.Bid, bidder, failedAttributes)
+		}
+	}
+
+	changeSet := hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{}
+	if len(payload.Bids) != len(allowedBids) {
+		changeSet.RawBidderResponse().Bids().Update(allowedBids)
+		result.ChangeSet = changeSet
+	}
+
+	return result, err
+}
+
+func mediaTypesFromBid(bid *adapters.TypedBid) mediaTypes {
+	return mediaTypes{string(bid.BidType): struct{}{}}
+}
+
+func shouldBeBlockedDueToBadv(
+	result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
+	cfg config,
+	blockAttr blockingAttributes,
+	bid *openrtb2.Bid,
+	bidMediaTypes mediaTypes,
+	bidder string,
+	dealID string,
+	failedChecksData map[string]interface{},
+) (map[string]interface{}, error) {
+	badv := cfg.Attributes.Badv
+
+	enforceBlocks, message, err := firstOrDefaultOverride(bidder, bidMediaTypes, getIsActive, badv.ActionOverrides.EnforceBlocks, badv.EnforceBlocks)
+	result.Warnings = mergeStrings(result.Warnings, message)
+	if err != nil {
+		return failedChecksData, fmt.Errorf("failed to get override for badv.enforce_blocks: %s", err)
+	}
+	if !enforceBlocks {
+		return failedChecksData, nil
+	}
+
+	blockUnknown, message, err := firstOrDefaultOverride(bidder, bidMediaTypes, getIsActive, badv.ActionOverrides.BlockUnknownAdomain, badv.BlockUnknownAdomain)
+	result.Warnings = mergeStrings(result.Warnings, message)
+	if err != nil {
+		return failedChecksData, fmt.Errorf("failed to get override for badv.block_unknown_adomain: %s", err)
+	}
+
+	var dealExceptions []string
+	if dealID != "" {
+		dealExceptions, err = findDealExceptionsOverridesOrDefault(dealID, getNames, badv.ActionOverrides.AllowedAdomainForDeals, badv.AllowedAdomainForDeals)
+		if err != nil {
+			return failedChecksData, fmt.Errorf("failed to get override for badv.allowed_adomain_for_deals: %s", err)
+		}
+	}
+
+	shouldBlock, blockedAttributes := blockAttribute(bid.ADomain, blockAttr.bAdv, dealExceptions, blockUnknown)
+	if shouldBlock {
+		failedChecksData["badv"] = blockedAttributes
+	}
+
+	return failedChecksData, nil
+}
+
+func shouldBeBlockedDueToBcat(
+	result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
+	cfg config,
+	blockAttr blockingAttributes,
+	bid *openrtb2.Bid,
+	bidMediaTypes mediaTypes,
+	bidder, dealID string,
+	failedChecksData map[string]interface{},
+) (map[string]interface{}, bool, error) {
+	bcat := cfg.Attributes.Bcat
+
+	enforceBlocks, message, err := firstOrDefaultOverride(bidder, bidMediaTypes, getIsActive, bcat.ActionOverrides.EnforceBlocks, bcat.EnforceBlocks)
+	result.Warnings = mergeStrings(result.Warnings, message)
+	if err != nil {
+		return failedChecksData, false, fmt.Errorf("failed to get override for bcat.enforce_blocks: %s", err)
+	}
+	if !enforceBlocks {
+		return failedChecksData, false, nil
+	}
+
+	blockUnknown, message, err := firstOrDefaultOverride(bidder, bidMediaTypes, getIsActive, bcat.ActionOverrides.BlockUnknownAdvCat, bcat.BlockUnknownAdvCat)
+	result.Warnings = mergeStrings(result.Warnings, message)
+	if err != nil {
+		return failedChecksData, enforceBlocks, fmt.Errorf("failed to get override for bcat.block_unknown_adv_cat: %s", err)
+	}
+
+	var dealExceptions []string
+	if dealID != "" {
+		dealExceptions, err = findDealExceptionsOverridesOrDefault(dealID, getNames, bcat.ActionOverrides.AllowedAdvCatForDeals, bcat.AllowedAdvCatForDeals)
+		if err != nil {
+			return failedChecksData, enforceBlocks, fmt.Errorf("failed to get override for bcat.allowed_adv_cat_for_deals: %s", err)
+		}
+	}
+
+	shouldBlock, blockedAttributes := blockAttribute(bid.Cat, blockAttr.bCat, dealExceptions, blockUnknown)
+	if shouldBlock {
+		failedChecksData["bcat"] = blockedAttributes
+	}
+
+	return failedChecksData, enforceBlocks, nil
+}
+
+func shouldBeBlockedDueToCattax(
+	blockAttr blockingAttributes,
+	bid *openrtb2.Bid,
+	enforceBlocks bool,
+	failedChecksData map[string]interface{},
+) map[string]interface{} {
+	if !enforceBlocks {
+		return failedChecksData
+	}
+
+	cattax := bid.CatTax
+	if cattax == 0 {
+		return failedChecksData
+	}
+
+	// if blocking cattax was not specified use a default value
+	if blockAttr.catTax == 0 {
+		blockAttr.catTax = adcom1.CatTaxIABContent10
+	}
+
+	// cattax check has a reverse logic, the blockAttr.cattax should have one allowed value
+	if cattax != blockAttr.catTax {
+		failedChecksData["cattax"] = []adcom1.CategoryTaxonomy{cattax}
+	}
+
+	return failedChecksData
+}
+
+func shouldBeBlockedDueToBapp(
+	result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
+	cfg config,
+	blockAttr blockingAttributes,
+	bid *openrtb2.Bid,
+	bidMediaTypes mediaTypes,
+	bidder, dealID string,
+	failedChecksData map[string]interface{},
+) (map[string]interface{}, error) {
+	bapp := cfg.Attributes.Bapp
+
+	enforceBlocks, message, err := firstOrDefaultOverride(bidder, bidMediaTypes, getIsActive, bapp.ActionOverrides.EnforceBlocks, bapp.EnforceBlocks)
+	result.Warnings = mergeStrings(result.Warnings, message)
+	if err != nil {
+		return failedChecksData, fmt.Errorf("failed to get override for bapp.enforce_blocks: %s", err)
+	}
+	if !enforceBlocks {
+		return failedChecksData, nil
+	}
+
+	var dealExceptions []string
+	if dealID != "" {
+		dealExceptions, err = findDealExceptionsOverridesOrDefault(dealID, getNames, bapp.ActionOverrides.AllowedAppForDeals, bapp.AllowedAppForDeals)
+		if err != nil {
+			return failedChecksData, fmt.Errorf("failed to get override for bapp.allowed_app_for_deals: %s", err)
+		}
+	}
+
+	bidBapp := []string{bid.Bundle}
+
+	shouldBlock, blockedAttributes := blockAttribute(bidBapp, blockAttr.bApp, dealExceptions, false)
+	if shouldBlock {
+		failedChecksData["bapp"] = blockedAttributes
+	}
+
+	return failedChecksData, nil
+}
+
+func shouldBeBlockedDueToBattr(
+	result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
+	cfg config,
+	blockAttr blockingAttributes,
+	bid *openrtb2.Bid,
+	bidMediaTypes mediaTypes,
+	bidder, dealID string,
+	failedChecksData map[string]interface{},
+) (map[string]interface{}, error) {
+	battr := cfg.Attributes.Battr
+
+	enforceBlocks, message, err := firstOrDefaultOverride(bidder, bidMediaTypes, getIsActive, battr.ActionOverrides.EnforceBlocks, battr.EnforceBlocks)
+	result.Warnings = mergeStrings(result.Warnings, message)
+	if err != nil {
+		return failedChecksData, fmt.Errorf("failed to get override for battr.enforce_blocks: %s", err)
+	}
+	if !enforceBlocks {
+		return failedChecksData, nil
+	}
+
+	var dealExceptions []int
+	if dealID != "" {
+		dealExceptions, err = findDealExceptionsOverridesOrDefault(dealID, getIds, battr.ActionOverrides.AllowedBannerAttrForDeals, battr.AllowedBannerAttrForDeals)
+		if err != nil {
+			return failedChecksData, fmt.Errorf("failed to get override for battr.allowed_banner_attr_for_deals: %s", err)
+		}
+	}
+
+	if blockAttr.bAttr == nil || len(blockAttr.bAttr) == 0 {
+		return failedChecksData, nil
+	}
+	blockedBattr := blockAttr.bAttr[bid.ImpID]
+	bidAttr := toInt(bid.Attr)
+
+	shouldBlock, blockedAttributes := blockAttribute(bidAttr, blockedBattr, dealExceptions, false)
+	if shouldBlock {
+		failedChecksData["battr"] = blockedAttributes
+	}
+
+	return failedChecksData, nil
+}
+
+func blockAttribute[Attribute comparable](attributes, blockedAttributes, dealExceptions []Attribute, blockUnknown bool) (bool, []Attribute) {
+	if len(attributes) == 0 {
+		if blockUnknown {
+			return true, nil
+		}
+		return false, nil
+	}
+
+	if len(blockedAttributes) == 0 {
+		return false, nil
+	}
+
+	blockedElements := make([]Attribute, 0)
+	shouldBlock := true
+	for _, attribute := range attributes {
+		for _, blockedAttribute := range blockedAttributes {
+			if attribute == blockedAttribute {
+				for _, allowedAttribute := range dealExceptions {
+					if attribute == allowedAttribute {
+						shouldBlock = false
+						break
+					}
+				}
+				if shouldBlock {
+					blockedElements = append(blockedElements, attribute)
+				}
+			}
+			shouldBlock = true
+		}
+	}
+
+	if len(blockedElements) == 0 {
+		return false, nil
+	}
+
+	return true, blockedElements
+}
+
+func findDealExceptionsOverridesOrDefault[T any](
+	dealID string,
+	overrideGetter overrideGetterFn[[]T],
+	actionOverrides []ActionOverride,
+	defaultOverride []T,
+) (overrides []T, err error) {
+	for _, action := range actionOverrides {
+		if action.Conditions.DealIds == nil {
+			return overrides, errors.New("conditions field in account configuration must contain deal_ids")
+		}
+
+		for _, id := range action.Conditions.DealIds {
+			if id == dealID {
+				actionOverride, err := overrideGetter(action.Override)
+				if err != nil {
+					return overrides, err
+				}
+				overrides = append(overrides, actionOverride...)
+			}
+		}
+	}
+
+	if len(overrides) == 0 {
+		return defaultOverride, nil
+	}
+
+	overrides = append(overrides, defaultOverride...)
+
+	return overrides, nil
+}
+
+func addDebugMessage(
+	result *hookstage.HookResult[hookstage.RawBidderResponsePayload],
+	bid *openrtb2.Bid,
+	bidder string,
+	failedAttributes []string,
+) {
+	result.DebugMessages = append(
+		result.DebugMessages,
+		fmt.Sprintf("Bid %s from bidder %s has been rejected, failed checks: %s", bid.ID, bidder, strings.Join(failedAttributes, ", ")),
+	)
+}
+
+// returns a slice with the names of failed attributes
+func getFailedAttributes(data map[string]interface{}) []string {
+	var builder []string
+	for _, attribute := range [5]string{
+		"badv",
+		"bcat",
+		"cattax",
+		"bapp",
+		"battr",
+	} {
+		if _, ok := data[attribute]; ok {
+			builder = append(builder, attribute)
+		}
+	}
+
+	return builder
+}
diff --git a/modules/prebid/ortb2blocking/module.go b/modules/prebid/ortb2blocking/module.go
index 8f8c2968..1f83a9f6 100644
--- a/modules/prebid/ortb2blocking/module.go
+++ b/modules/prebid/ortb2blocking/module.go
@@ -35,6 +35,25 @@ func (m Module) HandleBidderRequestHook(
 	return handleBidderRequestHook(cfg, payload)
 }
 
+// HandleRawBidderResponseHook rejects bids for a specific bidder if they fail the attribute check.
+func (m Module) HandleRawBidderResponseHook(
+	_ context.Context,
+	miCtx hookstage.ModuleInvocationContext,
+	payload hookstage.RawBidderResponsePayload,
+) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) {
+	result := hookstage.HookResult[hookstage.RawBidderResponsePayload]{}
+	var cfg config
+	if len(miCtx.AccountConfig) != 0 {
+		ncfg, err := newConfig(miCtx.AccountConfig)
+		if err != nil {
+			return result, err
+		}
+		cfg = ncfg
+	}
+
+	return handleRawBidderResponseHook(cfg, payload, miCtx.ModuleContext)
+}
+
 type blockingAttributes struct {
 	bAdv   []string
 	bApp   []string
diff --git a/modules/prebid/ortb2blocking/utils.go b/modules/prebid/ortb2blocking/utils.go
index affd5a47..eb148b02 100644
--- a/modules/prebid/ortb2blocking/utils.go
+++ b/modules/prebid/ortb2blocking/utils.go
@@ -1,6 +1,11 @@
 package ortb2blocking
 
-import "strings"
+import (
+	"strings"
+
+	"github.com/prebid/openrtb/v17/adcom1"
+	"github.com/prebid/openrtb/v17/openrtb2"
+)
 
 func mergeStrings(messages []string, newMessages ...string) []string {
 	for _, msg := range newMessages {
@@ -20,3 +25,15 @@ func hasMatches(list []string, s string) bool {
 	}
 	return false
 }
+
+type numeric interface {
+	openrtb2.BannerAdType | adcom1.CreativeAttribute
+}
+
+func toInt[T numeric](values []T) []int {
+	ints := make([]int, len(values))
+	for i := range values {
+		ints[i] = int(values[i])
+	}
+	return ints
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 8f0fce6e..e6b20b25 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -136,6 +136,7 @@ const (
 	BidderBlue              BidderName = "blue"
 	BidderBmtm              BidderName = "bmtm"
 	BidderBoldwin           BidderName = "boldwin"
+	BidderBrave             BidderName = "brave"
 	BidderBrightroll        BidderName = "brightroll"
 	BidderCcx               BidderName = "ccx"
 	BidderCoinzilla         BidderName = "coinzilla"
@@ -310,6 +311,7 @@ func CoreBidderNames() []BidderName {
 		BidderBlue,
 		BidderBmtm,
 		BidderBoldwin,
+		BidderBrave,
 		BidderBrightroll,
 		BidderCcx,
 		BidderCoinzilla,
diff --git a/openrtb_ext/imp_brave.go b/openrtb_ext/imp_brave.go
new file mode 100644
index 00000000..2c7a0eaf
--- /dev/null
+++ b/openrtb_ext/imp_brave.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpBrave struct {
+	PlacementID string `json:"placementId"`
+}
diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml
index 06afe454..6686e7ac 100644
--- a/static/bidder-info/appnexus.yaml
+++ b/static/bidder-info/appnexus.yaml
@@ -1,7 +1,7 @@
 endpoint: "http://ib.adnxs.com/openrtb2"
 platform_id: "5"
 maintainer:
-  email: "prebid-server@xandr.com"
+  email: "prebid@microsoft.com"
 gvlVendorID: 32
 capabilities:
   app:
diff --git a/static/bidder-info/brave.yaml b/static/bidder-info/brave.yaml
new file mode 100644
index 00000000..1e4b7ced
--- /dev/null
+++ b/static/bidder-info/brave.yaml
@@ -0,0 +1,15 @@
+endpoint: "http://point.braveglobal.tv/?t=3&partner={{.PublisherID}}"
+maintainer:
+  email: "support@thebrave.io"
+gvlVendorID: 869
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
diff --git a/static/bidder-info/eplanning.yaml b/static/bidder-info/eplanning.yaml
index 614d0f3b..dd8b7eab 100644
--- a/static/bidder-info/eplanning.yaml
+++ b/static/bidder-info/eplanning.yaml
@@ -9,6 +9,7 @@ capabilities:
   site:
     mediaTypes:
       - banner
+      - video
 userSync:
   iframe:
     url: "https://ads.us.e-planning.net/uspd/1/?du={{.RedirectURL}}"
diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml
index ac0f8929..e1474f77 100644
--- a/static/bidder-info/mobilefuse.yaml
+++ b/static/bidder-info/mobilefuse.yaml
@@ -7,4 +7,5 @@ capabilities:
     mediaTypes:
       - banner
       - video
+      - native
 endpointCompression: "GZIP"
\ No newline at end of file
diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml
index 47d66458..c19d7674 100644
--- a/static/bidder-info/synacormedia.yaml
+++ b/static/bidder-info/synacormedia.yaml
@@ -12,11 +12,4 @@ capabilities:
       - banner
       - video
 userSync:
-  key: "imds"
-  supportCors: true
-  iframe:
-    url: "https://ad-cdn.technoratimedia.com/html/usersync.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
-    userMacro: "[USER_ID]"
-  redirect:
-    url: "https://sync.technoratimedia.com/services?srv=cs&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
-    userMacro: "[USER_ID]"
+  key: "imds"
\ No newline at end of file
diff --git a/static/bidder-params/brave.json b/static/bidder-params/brave.json
new file mode 100644
index 00000000..8e913982
--- /dev/null
+++ b/static/bidder-params/brave.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Brave Adapter Params",
+  "description": "A schema which validates params accepted by the Brave adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "description": "Unique Placement ID of publisher"
+    }
+  },
+  "required": ["placementId"]
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go
index 27e006e1..9c431c2a 100644
--- a/adapters/adnuntius/adnuntius_test.go
+++ b/adapters/adnuntius/adnuntius_test.go
@@ -13,7 +13,10 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderAdnuntius, config.Adapter{
-		Endpoint: "http://whatever.url"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+		Endpoint:         "http://whatever.url",
+		ExtraAdapterInfo: "http://gdpr.url",
+	},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -43,5 +46,4 @@ func AssignDefaultValues(bidder adapters.Bidder) {
 	bidderAdnuntius.time = &FakeTime{
 		time: time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC),
 	}
-	bidderAdnuntius.extraInfo = "http://gdpr.url"
 }
diff --git a/adapters/brave/brave_test.go b/adapters/brave/brave_test.go
new file mode 100644
index 00000000..e8d0a474
--- /dev/null
+++ b/adapters/brave/brave_test.go
@@ -0,0 +1,30 @@
+package brave
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderBrave, config.Adapter{
+		Endpoint: "http://point.braveglobal.tv/?t=3&partner={{.PublisherID}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "bravetest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderBrave, config.Adapter{
+		Endpoint: "{{Malformed}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/brave/bravetest/exemplary/banner-app.json b/adapters/brave/bravetest/exemplary/banner-app.json
new file mode 100644
index 00000000..71a30978
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/banner-app.json
@@ -0,0 +1,140 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB9-1"
+        ],
+        "bundle": "com.app.test",
+        "name": "Test App",
+        "id": "12345678"
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "banner": {
+            "w":320,
+            "h":50
+          },
+          "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+          "headers": {
+            "Content-Type": [
+              "application/json;charset=utf-8"
+            ],
+            "Accept": [
+              "application/json"
+            ]
+          },
+          "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+          "body": {
+            "id": "request-id",
+            "device": {
+              "ua": "useragent",
+              "ip": "100.100.100.100",
+              "language": "en"
+            },
+            "imp": [
+              {
+                "id": "impression-id",
+                "banner": {
+                    "w":320,
+                    "h":50
+                },
+                "tagid": "tid"
+              }
+            ],
+            "app": {
+              "publisher": {
+                "id": "123456789"
+              },
+              "cat": [
+                "IAB9-1"
+              ],
+              "bundle": "com.app.test",
+              "name": "Test App",
+              "id": "12345678"
+            },
+            "user": {
+              "id": "some-user"
+            },
+            "tmax": 1000
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100",
+                    "w":320,
+                    "h":50
+                  }
+                ],
+              "type": "banner",
+                "seat": "brave"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "brave": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100",
+            "w":320,
+            "h":50
+          },
+          "type": "banner"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/brave/bravetest/exemplary/banner-web.json b/adapters/brave/bravetest/exemplary/banner-web.json
new file mode 100644
index 00000000..0dd6dde0
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/banner-web.json
@@ -0,0 +1,138 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "f897beb0daba0253d8e59a098eef9311"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "banner": {
+                  "w":320,
+                  "h":50
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w":320,
+                  "h":50
+                }
+              ],
+            "type": "banner",
+              "seat": "brave"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w":320,
+          "h":50
+        },
+        "type": "banner"
+      }]
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/exemplary/native-app.json b/adapters/brave/bravetest/exemplary/native-app.json
new file mode 100644
index 00000000..05ac5aa8
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/native-app.json
@@ -0,0 +1,137 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB9-1"
+        ],
+        "bundle": "com.app.test",
+        "name": "Test App",
+        "id": "12345678"
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+	        "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+            "headers": {
+              "Content-Type": [
+                "application/json;charset=utf-8"
+              ],
+              "Accept": [
+                "application/json"
+              ]
+            },
+            "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+            "body": {
+              "id": "request-id",
+              "device": {
+                "ua": "useragent",
+                "ip": "100.100.100.100",
+                "language": "en"
+              },
+              "imp": [
+                {
+                  "id": "impression-id",
+                  "native": {
+                    "ver":"1.1",
+                    "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "tagid": "tid"
+                }
+              ],
+              "app": {
+                "publisher": {
+                  "id": "123456789"
+                },
+                "cat": [
+                  "IAB9-1"
+                ],
+                "bundle": "com.app.test",
+                "name": "Test App",
+                "id": "12345678"
+              },
+              "user": {
+                "id": "some-user"
+              },
+              "tmax": 1000
+            }
+        },
+        "mockResponse": {
+
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100"
+                  }
+                ],
+                "type": "native",
+                "seat": "brave"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "brave": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100"
+          },
+          "type": "native"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/brave/bravetest/exemplary/native-web.json b/adapters/brave/bravetest/exemplary/native-web.json
new file mode 100644
index 00000000..01126e46
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/native-web.json
@@ -0,0 +1,135 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "site": {
+        "page": "test.com/page",
+        "domain": "test.com",
+        "cat": [
+          "IAB9-1"
+        ],
+        "publisher": {
+          "id": "123456789"
+        }
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+	        "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+            "headers": {
+              "Content-Type": [
+                "application/json;charset=utf-8"
+              ],
+              "Accept": [
+                "application/json"
+              ]
+            },
+            "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+            "body": {
+              "id": "request-id",
+              "device": {
+                "ua": "useragent",
+                "ip": "100.100.100.100",
+                "language": "en"
+              },
+              "imp": [
+                {
+                  "id": "impression-id",
+                  "native": {
+                    "ver":"1.1",
+                    "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "tagid": "tid"
+                }
+              ],
+              "site": {
+                "page": "test.com/page",
+                "domain": "test.com",
+                "cat": [
+                  "IAB9-1"
+                ],
+                "publisher": {
+                  "id": "123456789"
+                }
+              },
+              "user": {
+                "id": "some-user"
+              },
+              "tmax": 1000
+            }
+        },
+        "mockResponse": {
+
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100"
+                  }
+                ],
+                "type": "native",
+                "seat": "brave"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "brave": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100"
+          },
+          "type": "native"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/brave/bravetest/exemplary/video-app.json b/adapters/brave/bravetest/exemplary/video-app.json
new file mode 100644
index 00000000..7f415109
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/video-app.json
@@ -0,0 +1,150 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "app": {
+            "publisher": {
+              "id": "123456789"
+            },
+            "cat": [
+              "IAB9-1"
+            ],
+            "bundle": "com.app.test",
+            "name": "Test App",
+            "id": "12345678"
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+            "type": "video",
+            "seat": "brave"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w": 1280,
+          "h": 720
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/exemplary/video-web.json b/adapters/brave/bravetest/exemplary/video-web.json
new file mode 100644
index 00000000..5da8ff35
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/video-web.json
@@ -0,0 +1,148 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+            "type": "video",
+            "seat": "brave"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w": 1280,
+          "h": 720
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/empty-seatbid-array.json b/adapters/brave/bravetest/supplemental/empty-seatbid-array.json
new file mode 100644
index 00000000..c407c822
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/empty-seatbid-array.json
@@ -0,0 +1,121 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    },
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Empty SeatBid array",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/invalid-brave-ext-object.json b/adapters/brave/bravetest/supplemental/invalid-brave-ext-object.json
new file mode 100644
index 00000000..899834e6
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/invalid-brave-ext-object.json
@@ -0,0 +1,29 @@
+{
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "ext.bidder not provided",
+      "comparison": "literal"
+    }
+  ],
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": ["video/mp4"],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+        "ext": "invalid data"
+      }
+    ],
+    "site": {
+      "page": "test.com"
+    }
+  },
+  "httpCalls": []
+}
diff --git a/adapters/brave/bravetest/supplemental/invalid-response.json b/adapters/brave/bravetest/supplemental/invalid-response.json
new file mode 100644
index 00000000..972c2b30
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/invalid-response.json
@@ -0,0 +1,97 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "f897beb0daba0253d8e59a098eef9311"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "banner": {
+                "w":320,
+                "h":50
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Server Response",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-bad-request.json b/adapters/brave/bravetest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000..e80273a7
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-bad-request.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 400
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-no-content.json b/adapters/brave/bravetest/supplemental/status-code-no-content.json
new file mode 100644
index 00000000..eb369bd8
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-no-content.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 204
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "No bid",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-other-error.json b/adapters/brave/bravetest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000..bd68ee85
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-other-error.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 306
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Something went wrong, please contact your Account Manager. Status Code: [ 306 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-service-unavailable.json b/adapters/brave/bravetest/supplemental/status-code-service-unavailable.json
new file mode 100644
index 00000000..f66347d8
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-service-unavailable.json
@@ -0,0 +1,107 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 503
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Service Unavailable. Status Code: [ 503 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/params_test.go b/adapters/brave/params_test.go
new file mode 100644
index 00000000..1120a13c
--- /dev/null
+++ b/adapters/brave/params_test.go
@@ -0,0 +1,51 @@
+package brave
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderBrave, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Brave params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderBrave, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{ "placementId": "f897beb0daba0253d8e59a098eef9311" }`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
+	`{ "placementid": "f897beb0daba0253d8e59a098eef9311" }`,
+	`{ "PlacementId": "f897beb0daba0253d8e59a098eef9311" }`,
+}
diff --git a/adapters/eplanning/eplanningtest/supplemental/three-imps-1_banner-1_video_instream-and-1_video_outstream-only-send-video-instream.json b/adapters/eplanning/eplanningtest/supplemental/three-imps-1_banner-1_video_instream-and-1_video_outstream-only-send-video-instream.json
new file mode 100644
index 00000000..f0409bdc
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/three-imps-1_banner-1_video_instream-and-1_video_outstream-only-send-video-instream.json
@@ -0,0 +1,111 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "w": 600,
+            "h": 300
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_banner"
+            }
+          }
+        },
+        {
+          "id": "test-imp-instream",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 1
+          },
+          "ext": {
+            "bidder": {
+              "ci": "6789",
+              "adunit_code": "test_adunitcode_instream"
+            }
+          }
+        },
+        {
+          "id": "test-imp-outstream",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 5
+          },
+          "ext": {
+            "bidder": {
+              "ci": "6789",
+              "adunit_code": "test_adunitcode_outstream"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/6789/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1&ncb=1&ur=FILE&vctx=1&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-instream",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ],
+
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Ignoring imp id=test-imp-id, auction instream and imp no instream",
+        "comparison": "literal"
+      },
+      {
+        "value": "Ignoring imp id=test-imp-outstream, auction instream and imp no instream",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/two-imps-1_banner-and-1_video_instream-send-only-instream.json b/adapters/eplanning/eplanningtest/supplemental/two-imps-1_banner-and-1_video_instream-send-only-instream.json
new file mode 100644
index 00000000..29d09c0e
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/two-imps-1_banner-and-1_video_instream-send-only-instream.json
@@ -0,0 +1,92 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "w": 600,
+            "h": 300
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_banner"
+            }
+          }
+        },
+        {
+          "id": "test-imp-instream",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 1
+          },
+          "ext": {
+            "bidder": {
+              "ci": "6789",
+              "adunit_code": "test_adunitcode_instream"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/6789/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1&ncb=1&ur=FILE&vctx=1&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-instream",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ],
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Ignoring imp id=test-imp-id, auction instream and imp no instream",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/two-imps-1_banner-and-1_video_outstream-send-only-outstream.json b/adapters/eplanning/eplanningtest/supplemental/two-imps-1_banner-and-1_video_outstream-send-only-outstream.json
new file mode 100644
index 00000000..c8c3c4e5
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/two-imps-1_banner-and-1_video_outstream-send-only-outstream.json
@@ -0,0 +1,92 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "w": 600,
+            "h": 300
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_ad.-un(itco:de:"
+            }
+          }
+        },
+        {
+          "id": "test-imp-outstream",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 5
+          },
+          "ext": {
+            "bidder": {
+              "ci": "6789",
+              "adunit_code": "test_adunitcode_outstream"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/6789/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1&ncb=1&ur=FILE&vctx=2&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "6789" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-outstream",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ],
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Ignoring imp id=test-imp-id, auction outstream and imp no outstream",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/two-imps-video-1_instream-and-1_outstream-send-only-instream.json b/adapters/eplanning/eplanningtest/supplemental/two-imps-video-1_instream-and-1_outstream-send-only-instream.json
new file mode 100644
index 00000000..078a0511
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/two-imps-video-1_instream-and-1_outstream-send-only-instream.json
@@ -0,0 +1,93 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-instream",
+          "video": {
+            "w": 300,
+            "h": 150,
+            "placement": 1
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_instream"
+            }
+          }
+        },
+        {
+          "id": "test-imp-outstream",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 5
+          },
+          "ext": {
+            "bidder": {
+              "ci": "6789",
+              "adunit_code": "test_adunitcode_outstream"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=video_300x150_0%3A300x150%3B1&ncb=1&ur=FILE&vctx=1&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_300x150_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 300,
+                  "h": 150
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-instream",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 300,
+              "h": 150
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ],
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Ignoring imp id=test-imp-outstream, auction instream and imp no instream",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/two-imps-video-instream.json b/adapters/eplanning/eplanningtest/supplemental/two-imps-video-instream.json
new file mode 100644
index 00000000..5e6df243
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/two-imps-video-instream.json
@@ -0,0 +1,112 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-instream-1",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 1
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_instream_1"
+            }
+          }
+        },
+        {
+          "id": "test-imp-instream-2",
+          "video": {
+            "w": 640,
+            "h": 480,
+            "placement": 1
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_instream_2"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1%2Bvideo_640x480_1%3A640x480%3B1&ncb=1&ur=FILE&vctx=1&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdefg",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef1234567810",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              },
+              {
+                "k": "video_640x480_1",
+                "a": [{
+                  "i": "023456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test2</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid789",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdefg",
+              "impid": "test-imp-instream-1",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef1234567810",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          },
+          {
+            "bid": {
+              "id": "023456789abcdef",
+              "impid": "test-imp-instream-2",
+              "price": 0.5,
+              "adm": "<VAST>test2</VAST>",
+              "adid": "adid789",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/video-no-placement-send-vctx-2.json b/adapters/eplanning/eplanningtest/supplemental/video-no-placement-send-vctx-2.json
new file mode 100644
index 00000000..877fb874
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/video-no-placement-send-vctx-2.json
@@ -0,0 +1,72 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "video": {
+            "w": 300,
+            "h": 150
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_nosize"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=video_300x150_0%3A300x150%3B1&ncb=1&ur=FILE&vctx=2&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_300x150_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 300,
+                  "h": 150
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-id",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 300,
+              "h": 150
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/video-no-size-send-640x480.json b/adapters/eplanning/eplanningtest/supplemental/video-no-size-send-640x480.json
new file mode 100644
index 00000000..62efdc5c
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/video-no-size-send-640x480.json
@@ -0,0 +1,70 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "video": {
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_nosize"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1&ncb=1&ur=FILE&vctx=2&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-id",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/video-placement-1-send-vctx-1.json b/adapters/eplanning/eplanningtest/supplemental/video-placement-1-send-vctx-1.json
new file mode 100644
index 00000000..62efdc5c
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/video-placement-1-send-vctx-1.json
@@ -0,0 +1,70 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "video": {
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_nosize"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1&ncb=1&ur=FILE&vctx=2&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-id",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
+  
diff --git a/adapters/eplanning/eplanningtest/supplemental/video-placement-no-1-send-vctx-2.json b/adapters/eplanning/eplanningtest/supplemental/video-placement-no-1-send-vctx-2.json
new file mode 100644
index 00000000..62efdc5c
--- /dev/null
+++ b/adapters/eplanning/eplanningtest/supplemental/video-placement-no-1-send-vctx-2.json
@@ -0,0 +1,70 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "video": {
+          },
+          "ext": {
+            "bidder": {
+              "ci": "12345",
+              "adunit_code": "test_adunitcode_nosize"
+            }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://rtb.e-planning.net/pbs/1/12345/1/FILE/ROS?e=video_640x480_0%3A640x480%3B1&ncb=1&ur=FILE&vctx=2&vv=3",
+          "body": {}
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "sI": { "k": "12345" },
+            "sec": "ROS",
+            "sp": [
+              {
+                "k": "video_640x480_0",
+                "a": [{
+                  "i": "123456789abcdef",
+                  "pr": "0.5",
+                  "adm": "<VAST>test</VAST>",
+                  "crid": "abcdef123456789",
+                  "id": "adid12345",
+                  "w": 640,
+                  "h": 480
+                }]
+              }
+            ]
+          }
+        }
+      }
+    ],
+  
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "123456789abcdef",
+              "impid": "test-imp-id",
+              "price": 0.5,
+              "adm": "<VAST>test</VAST>",
+              "adid": "adid12345",
+              "crid": "abcdef123456789",
+              "w": 640,
+              "h": 480
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
+  
diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go
index a4940ef3..b962bb73 100644
--- a/adapters/ix/ix_test.go
+++ b/adapters/ix/ix_test.go
@@ -104,6 +104,56 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) {
 	}
 }
 
+func TestIxMakeRequestWithGppString(t *testing.T) {
+	bidder := &IxAdapter{}
+	bidder.maxRequests = 2
+
+	testGppString := "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN"
+
+	mockedReq := &openrtb2.BidRequest{
+		Imp: []openrtb2.Imp{{
+			ID: "1_1",
+			Video: &openrtb2.Video{
+				W:           640,
+				H:           360,
+				MIMEs:       []string{"video/mp4"},
+				MaxDuration: 60,
+				Protocols:   []adcom1.MediaCreativeSubtype{2, 3, 5, 6},
+			},
+			Ext: json.RawMessage(
+				`{
+					"prebid": {},
+					"bidder": {
+						"siteID": 123456
+					}
+				}`,
+			)},
+		},
+		Regs: &openrtb2.Regs{
+			GPP: testGppString,
+		},
+	}
+
+	expectedRequestCount := 1
+	expectedErrorCount := 0
+	var reqInfo *adapters.ExtraRequestInfo
+
+	requests, errors := bidder.MakeRequests(mockedReq, reqInfo)
+
+	if len(requests) != expectedRequestCount {
+		t.Errorf("should have 1 request, requests=%v", requests)
+	}
+
+	if len(errors) != expectedErrorCount {
+		t.Errorf("should not have any errors, errors=%v", errors)
+	}
+
+	req := &openrtb2.BidRequest{}
+	json.Unmarshal(requests[0].Body, req)
+
+	assert.Equal(t, req.Regs.GPP, testGppString)
+}
+
 func TestBuildIxDiag(t *testing.T) {
 	testCases := []struct {
 		description     string
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/simple-native.json b/adapters/mobilefuse/mobilefusetest/exemplary/simple-native.json
new file mode 100644
index 00000000..a23a1a39
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/simple-native.json
@@ -0,0 +1,87 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "native": {
+                    "ver": "1.2",
+                    "request": "{\"native\":{\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}"
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 123456,
+                        "pub_id": 1234
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx.mobilefuse.com/openrtb?pub_id=1234",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "native": {
+                                "ver": "1.2",
+                                "request": "{\"native\":{\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}"
+                            },
+                            "tagid": "123456"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "mobilefuse",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 1.50,
+                                    "adm": "{\"native\":{\"link\":{\"url\":\"http://mfx.mobilefuse.com/openrtb\"},\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}",
+                                    "crid": "test-crid",
+                                    "h": 50,
+                                    "w": 320,
+                                    "ext": {
+                                        "mf": {
+                                            "media_type": "native"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 1.50,
+                        "adm": "{\"native\":{\"link\":{\"url\":\"http://mfx.mobilefuse.com/openrtb\"},\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"url\":\"...\"}},{\"id\":1,\"required\":1,\"title\":{\"text\":\"custom title\"}}]}}",
+                        "crid": "test-crid",
+                        "h": 50,
+                        "w": 320
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/openx/openxtest/exemplary/fledge-no-bids.json b/adapters/openx/openxtest/exemplary/fledge-no-bids.json
new file mode 100644
index 00000000..5c09b9ae
--- /dev/null
+++ b/adapters/openx/openxtest/exemplary/fledge-no-bids.json
@@ -0,0 +1,88 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "ae": 1,
+          "bidder": {
+            "unit": "539439964",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.openx.net/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "tagid": "539439964",
+              "ext": {
+                "ae": 1
+              }
+            }
+          ],
+          "ext": {
+            "bc": "hb_pbs_1.0.0",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "cur": "USD",
+          "nbr": 1,
+          "ext": {
+            "fledge_auction_configs": {
+              "test-request-id": {
+                "seller": "openx.com",
+                "interestGroupBuyers": ["buyer1.com"],
+                "sellerTimeout": 0,
+                "perBuyerSignals": {
+                  "buyer1.com": [1,"two",3,4, {}]
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [],
+      "fledgeauctionconfigs": [
+        {
+          "impid": "test-request-id",
+          "bidder": "openx",
+          "config": {
+            "seller": "openx.com",
+            "interestGroupBuyers": ["buyer1.com"],
+            "sellerTimeout": 0,
+            "perBuyerSignals": {
+              "buyer1.com": [1,"two",3,4, {}]
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/openx/openxtest/exemplary/fledge.json b/adapters/openx/openxtest/exemplary/fledge.json
new file mode 100644
index 00000000..9c8074b3
--- /dev/null
+++ b/adapters/openx/openxtest/exemplary/fledge.json
@@ -0,0 +1,114 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "ae": 1,
+          "bidder": {
+            "unit": "539439964",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.openx.net/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "tagid": "539439964",
+              "ext": {
+                "ae": 1
+              }
+            }
+          ],
+          "ext": {
+            "bc": "hb_pbs_1.0.0",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "openx",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "fledge_auction_configs": {
+              "test-request-id": {
+                "seller": "openx.com",
+                "interestGroupBuyers": ["buyer1.com"],
+                "sellerTimeout": 0,
+                "perBuyerSignals": {
+                  "buyer1.com": [1,"two",3,4, {}]
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ],
+      "fledgeauctionconfigs": [
+        {
+          "impid": "test-request-id",
+          "bidder": "openx",
+          "config": {
+            "seller": "openx.com",
+            "interestGroupBuyers": ["buyer1.com"],
+            "sellerTimeout": 0,
+            "perBuyerSignals": {
+              "buyer1.com": [1,"two",3,4, {}]
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/exchange/price_granularity_test.go b/exchange/price_granularity_test.go
index 13840838..6349713e 100644
--- a/exchange/price_granularity_test.go
+++ b/exchange/price_granularity_test.go
@@ -30,6 +30,22 @@ func TestGetPriceBucketString(t *testing.T) {
 		},
 	}
 
+	custom2 := openrtb_ext.PriceGranularity{
+		Precision: 2,
+		Ranges: []openrtb_ext.GranularityRange{
+			{
+				Min:       0.0,
+				Max:       1.5,
+				Increment: 1.0,
+			},
+			{
+				Min:       1.5,
+				Max:       10.0,
+				Increment: 1.2,
+			},
+		},
+	}
+
 	// Define test cases
 	type aTest struct {
 		granularityId       string
@@ -51,6 +67,7 @@ func TestGetPriceBucketString(t *testing.T) {
 				{"auto", auto, "1.85"},
 				{"dense", dense, "1.87"},
 				{"custom1", custom1, "1.86"},
+				{"custom2", custom2, "1.50"},
 			},
 		},
 		{
@@ -63,6 +80,15 @@ func TestGetPriceBucketString(t *testing.T) {
 				{"auto", auto, "5.70"},
 				{"dense", dense, "5.70"},
 				{"custom1", custom1, "5.70"},
+				{"custom2", custom2, "5.10"},
+			},
+		},
+		{
+			groupDesc: "cpm equal the max for custom granularity",
+			cpm:       10,
+			testCases: []aTest{
+				{"custom1", custom1, "10.00"},
+				{"custom2", custom2, "9.90"},
 			},
 		},
 		{
diff --git a/hooks/hookexecution/executor_test.go b/hooks/hookexecution/executor_test.go
index f0c3ec66..33553bbd 100644
--- a/hooks/hookexecution/executor_test.go
+++ b/hooks/hookexecution/executor_test.go
@@ -384,8 +384,9 @@ func TestMetricsAreGatheredDuringHookExecution(t *testing.T) {
 	metricEngine := &metrics.MetricsEngineMock{}
 	builder := TestAllHookResultsBuilder{}
 	exec := NewHookExecutor(TestAllHookResultsBuilder{}, "/openrtb2/auction", metricEngine)
+	moduleName := "module.x-1"
 	moduleLabels := metrics.ModuleLabels{
-		Module: "module-1",
+		Module: moduleReplacer.Replace(moduleName),
 		Stage:  "entrypoint",
 	}
 	rTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 }
@@ -2576,12 +2577,12 @@ func (e TestAllHookResultsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[h
 		hooks.Group[hookstage.Entrypoint]{
 			Timeout: 10 * time.Millisecond,
 			Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
-				{Module: "module-1", Code: "code-1", Hook: mockUpdateHeaderEntrypointHook{}},
-				{Module: "module-1", Code: "code-3", Hook: mockTimeoutHook{}},
-				{Module: "module-1", Code: "code-4", Hook: mockFailureHook{}},
-				{Module: "module-1", Code: "code-5", Hook: mockErrorHook{}},
-				{Module: "module-1", Code: "code-6", Hook: mockFailedMutationHook{}},
-				{Module: "module-1", Code: "code-7", Hook: mockModuleContextHook{key: "key", val: "val"}},
+				{Module: "module.x-1", Code: "code-1", Hook: mockUpdateHeaderEntrypointHook{}},
+				{Module: "module.x-1", Code: "code-3", Hook: mockTimeoutHook{}},
+				{Module: "module.x-1", Code: "code-4", Hook: mockFailureHook{}},
+				{Module: "module.x-1", Code: "code-5", Hook: mockErrorHook{}},
+				{Module: "module.x-1", Code: "code-6", Hook: mockFailedMutationHook{}},
+				{Module: "module.x-1", Code: "code-7", Hook: mockModuleContextHook{key: "key", val: "val"}},
 			},
 		},
 		// place the reject hook in a separate group because it rejects the stage completely
@@ -2589,7 +2590,7 @@ func (e TestAllHookResultsBuilder) PlanForEntrypointStage(_ string) hooks.Plan[h
 		hooks.Group[hookstage.Entrypoint]{
 			Timeout: 10 * time.Second,
 			Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
-				{Module: "module-1", Code: "code-2", Hook: mockRejectHook{}},
+				{Module: "module.x-1", Code: "code-2", Hook: mockRejectHook{}},
 			},
 		},
 	}
diff --git a/modules/prebid/ortb2blocking/module_test.go b/modules/prebid/ortb2blocking/module_test.go
index 654790c9..ce3bba00 100644
--- a/modules/prebid/ortb2blocking/module_test.go
+++ b/modules/prebid/ortb2blocking/module_test.go
@@ -8,6 +8,8 @@ import (
 
 	"github.com/prebid/openrtb/v17/adcom1"
 	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/hooks/hookanalytics"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/hooks/hookstage"
 	"github.com/prebid/prebid-server/modules/moduledeps"
@@ -277,6 +279,9 @@ const bAttr8 adcom1.CreativeAttribute = 8
 const bAttr9 adcom1.CreativeAttribute = 9
 const bAttr10 adcom1.CreativeAttribute = 10
 
+const impID1 = "Some-impid-1"
+const impID2 = "Some-impid-2"
+
 func TestHandleBidderRequestHook(t *testing.T) {
 	testCases := []struct {
 		description        string
@@ -594,14 +599,923 @@ func TestHandleBidderRequestHook(t *testing.T) {
 	}
 }
 
-type numeric interface {
-	openrtb2.BannerAdType | adcom1.CreativeAttribute
-}
+func TestHandleRawBidderResponseHook(t *testing.T) {
+	testCases := []struct {
+		description        string
+		payload            hookstage.RawBidderResponsePayload
+		config             json.RawMessage
+		moduleCtx          hookstage.ModuleContext
+		expectedBids       []*adapters.TypedBid
+		expectedHookResult hookstage.HookResult[hookstage.RawBidderResponsePayload]
+		expectedError      error
+	}{
+		{
+			description: "Payload not changed when empty account config and empty module contexts are provided. Analytic tags have successful records",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1},
+				},
+			}},
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Catch error if wrong data has been passed from previous hook. Payload not changed",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1},
+				},
+			}},
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ADomain: []string{"foo"}, ImpID: impID1},
+				},
+			},
+			moduleCtx:     map[string]interface{}{bidder: "boo"},
+			expectedError: hookexecution.NewFailure("could not cast blocking attributes for bidder `appnexus`, module context has incorrect data"),
+		},
+		{
+			description: "Bid blocked by badv attribute check. Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"badv"},
+										badvAnalyticKey:       []string{"forbidden_domain"},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: badv"},
+			},
+		},
+		{
+			description: "Bid not blocked because blocking conditions for current bidder do not exist. Payload not updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{"other-bidder": blockingAttributes{bAdv: []string{"forbidden_domain"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Bid not blocked because enforce blocking is disabled by account config. Payload not updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": false}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Bid not blocked because enforce blocking overridden for given bidder. Payload not updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Bid blocked by badv attribute check (block unknown attributes). Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"badv"},
+										badvAnalyticKey:       []string(nil),
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: badv"},
+			},
+		},
+		{
+			description: "Bid not blocked because block unknown overridden for given bidder. Payload not updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "block_unknown_adomain": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {"bidders": ["appnexus"]}, "override": false}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Bid not blocked due to deal exception. Payload not updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {"deal_ids": ["acceptDealID"]}, "override": ["forbidden_domain"]}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, ImpID: impID1, DealID: "acceptDealID"},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", ADomain: []string{"good_domain"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAdv: []string{"forbidden_domain"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "Expect error if there is an issue processing enforce blocks overrides for badv attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process badv block checking: failed to get override for badv.enforce_blocks: bidders and media_types absent from conditions, at least one of the fields must be present"),
+		},
+		{
+			description: "Expect error if there is an issue processing block unknown domains overrides for badv attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"block_unknown_adomain": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process badv block checking: failed to get override for badv.block_unknown_adomain: bidders and media_types absent from conditions, at least one of the fields must be present"),
+		},
+		{
+			description: "Expect error if deal_ids not defined in config override conditions for badv attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"badv": {"enforce_blocks": true, "action_overrides": {"allowed_adomain_for_deals": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", ADomain: []string{"forbidden_domain"}, DealID: "acceptDealID"},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process badv block checking: failed to get override for badv.allowed_adomain_for_deals: conditions field in account configuration must contain deal_ids"),
+		},
+		{
+			description: "Bid blocked by bcat attribute check. Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "2", Cat: []string{"moto"}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bCat: []string{"fishing"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"bcat"},
+										"bcat":                []string{"fishing"},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: bcat"},
+			},
+		},
+		{
+			description: "Expect error if there is an issue processing enforce blocks overrides for bcat attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process bcat block checking: failed to get override for bcat.enforce_blocks: bidders and media_types absent from conditions, at least one of the fields must be present"),
+		},
+		{
+			description: "Expect error if there is an issue processing block unknown domains overrides for bcat attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"block_unknown_adv_cat": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process bcat block checking: failed to get override for bcat.block_unknown_adv_cat: bidders and media_types absent from conditions, at least one of the fields must be present"),
+		},
+		{
+			description: "Expect error if deal_ids not defined in config override conditions for bcat attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"bcat": {"enforce_blocks": true, "action_overrides": {"allowed_adv_cat_for_deals": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Cat: []string{"fishing"}, ImpID: impID1, DealID: "acceptDealID"},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process bcat block checking: failed to get override for bcat.allowed_adv_cat_for_deals: conditions field in account configuration must contain deal_ids"),
+		},
+		{
+			description: "Bid blocked by cattax attribute check. Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{catTax: 2}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"cattax"},
+										cattaxAnalyticKey:     []adcom1.CategoryTaxonomy{1},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: cattax"},
+			},
+		},
+		{
+			description: "Bid blocked by cattax attribute check (the default value used if no blocking attribute passed). Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", CatTax: 2, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"bcat":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", CatTax: 1, ImpID: impID1},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"cattax"},
+										cattaxAnalyticKey:     []adcom1.CategoryTaxonomy{2},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 2 from bidder appnexus has been rejected, failed checks: cattax"},
+			},
+		},
+		{
+			description: "Bid blocked by bapp attribute check. Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"bapp":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "2", Bundle: "allowed_bundle", ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bApp: []string{"forbidden_bundle"}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"bapp"},
+										bappAnalyticKey:       []string{"forbidden_bundle"},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: bapp"},
+			},
+		},
+		{
+			description: "Expect error if there is an issue processing enforce blocks overrides for bapp attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process bapp block checking: failed to get override for bapp.enforce_blocks: bidders and media_types absent from conditions, at least one of the fields must be present"),
+		},
+		{
+			description: "Expect error if deal_ids not defined in config override conditions for bapp attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"bapp": {"enforce_blocks": true, "action_overrides": {"allowed_app_for_deals": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Bundle: "forbidden_bundle", ImpID: impID1, DealID: "acceptDealID"},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process bapp block checking: failed to get override for bapp.allowed_app_for_deals: conditions field in account configuration must contain deal_ids"),
+		},
+		{
+			description: "Bid blocked by battr attribute check. Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1},
+				},
+				{
+					Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"battr":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "2", Attr: []adcom1.CreativeAttribute{2}, ImpID: impID2},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{bAttr: map[string][]int{impID1: {1}}}},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"battr"},
+										battrAnalyticKey:      []int{1},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: battr"},
+			},
+		},
+		{
+			description: "Expect error if there is an issue processing enforce blocks overrides for battr attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"enforce_blocks": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process battr block checking: failed to get override for battr.enforce_blocks: bidders and media_types absent from conditions, at least one of the fields must be present"),
+		},
+		{
+			description: "Expect error if deal_ids not defined in config override conditions for battr attribute. Analytics should have error status tag",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"},
+				},
+			}},
+			config: json.RawMessage(`{"attributes": {"battr": {"enforce_blocks": true, "action_overrides": {"allowed_banner_attr_for_deals": [{"conditions": {}}]}}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{ID: "1", Attr: []adcom1.CreativeAttribute{1}, ImpID: impID1, DealID: "acceptDealID"},
+				},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusError,
+						},
+					},
+				},
+			},
+			expectedError: hookexecution.NewFailure("failed to process battr block checking: failed to get override for battr.allowed_banner_attr_for_deals: conditions field in account configuration must contain deal_ids"),
+		},
+		{
+			description: "Bid blocked by multiple attribute check. Payload updated. Analytic tags successfully collected",
+			payload: hookstage.RawBidderResponsePayload{Bidder: bidder, Bids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						ID:      "1",
+						ADomain: []string{"forbidden_domain"},
+						Cat:     []string{"fishing"},
+						CatTax:  1,
+						Bundle:  "forbidden_bundle",
+						Attr:    []adcom1.CreativeAttribute{1},
+						ImpID:   impID1,
+					},
+				},
+				{
+					Bid: &openrtb2.Bid{
+						ID:      "2",
+						ADomain: []string{"allowed_domain"},
+						Cat:     []string{"moto"},
+						CatTax:  2,
+						Bundle:  "allowed_bundle",
+						Attr:    []adcom1.CreativeAttribute{2},
+						ImpID:   impID2,
+					},
+				},
+			}},
+			config: json.RawMessage(`{"attributes":{"badv":{"enforce_blocks": true}, "bcat":{"enforce_blocks": true}, "cattax":{"enforce_blocks": true}, "bapp":{"enforce_blocks": true}, "battr":{"enforce_blocks": true}}}`),
+			expectedBids: []*adapters.TypedBid{
+				{
+					Bid: &openrtb2.Bid{
+						ID:      "2",
+						ADomain: []string{"allowed_domain"},
+						Cat:     []string{"moto"},
+						CatTax:  2,
+						Bundle:  "allowed_bundle",
+						Attr:    []adcom1.CreativeAttribute{2},
+						ImpID:   impID2,
+					},
+				},
+			},
+			moduleCtx: map[string]interface{}{bidder: blockingAttributes{
+				bAdv:   []string{"forbidden_domain"},
+				bCat:   []string{"fishing"},
+				catTax: 2,
+				bApp:   []string{"forbidden_bundle"},
+				bAttr:  map[string][]int{impID1: {1}}},
+			},
+			expectedHookResult: hookstage.HookResult[hookstage.RawBidderResponsePayload]{
+				AnalyticsTags: hookanalytics.Analytics{
+					Activities: []hookanalytics.Activity{
+						{
+							Name:   enforceBlockingTag,
+							Status: hookanalytics.ActivityStatusSuccess,
+							Results: []hookanalytics.Result{
+								{
+									Status: hookanalytics.ResultStatusBlock,
+									Values: map[string]interface{}{
+										attributesAnalyticKey: []string{"badv", "bcat", "cattax", "bapp", "battr"},
+										badvAnalyticKey:       []string{"forbidden_domain"},
+										cattaxAnalyticKey:     []adcom1.CategoryTaxonomy{1},
+										bappAnalyticKey:       []string{"forbidden_bundle"},
+										battrAnalyticKey:      []int{1},
+									},
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID1}},
+								},
+								{
+									Status:    hookanalytics.ResultStatusAllow,
+									AppliedTo: hookanalytics.AppliedTo{Bidder: bidder, ImpIds: []string{impID2}},
+								},
+							},
+						},
+					},
+				},
+				DebugMessages: []string{"Bid 1 from bidder appnexus has been rejected, failed checks: badv, bcat, cattax, bapp, battr"},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.description, func(t *testing.T) {
+			result, err := Builder(nil, moduledeps.ModuleDeps{})
+			assert.NoError(t, err, "Failed to build module.")
+
+			module, ok := result.(Module)
+			assert.True(t, ok, "Failed to cast module type.")
+
+			hookResult, err := module.HandleRawBidderResponseHook(
+				context.Background(),
+				hookstage.ModuleInvocationContext{
+					AccountConfig: test.config,
+					Endpoint:      hookexecution.EndpointAuction,
+					ModuleContext: test.moduleCtx,
+				},
+				test.payload,
+			)
+			assert.Equal(t, test.expectedError, err, "Invalid hook execution error.")
 
-func toInt[T numeric](values []T) []int {
-	ints := make([]int, len(values))
-	for i := range values {
-		ints[i] = int(values[i])
+			// test mutations separately
+			for _, mut := range hookResult.ChangeSet.Mutations() {
+				newPayload, err := mut.Apply(test.payload)
+				assert.NoError(t, err)
+				test.payload = newPayload
+			}
+			assert.Equal(t, test.expectedBids, test.payload.Bids, "Invalid Bids returned after executing RawBidderResponse hook.")
+
+			// reset ChangeSet not to break hookResult assertion, we validated ChangeSet separately
+			hookResult.ChangeSet = hookstage.ChangeSet[hookstage.RawBidderResponsePayload]{}
+			assert.Equal(t, test.expectedHookResult, hookResult, "Invalid hook execution result.")
+		})
 	}
-	return ints
 }