Repository: prebid/prebid-server
Pull request #2672 (2023-03-31 00:01:52)

FPD for amp endpoint
Implementation of https://github.com/prebid/prebid-server/issues/1727


Patch:
diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go
index aa093ec1..5c283f18 100644
--- a/adapters/appnexus/appnexus.go
+++ b/adapters/appnexus/appnexus.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"math/rand"
 	"net/http"
 	"strconv"
 	"strings"
@@ -14,6 +13,7 @@ import (
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/util/httputil"
+	"github.com/prebid/prebid-server/util/randomutil"
 
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/errortypes"
@@ -24,18 +24,43 @@ import (
 const defaultPlatformID int = 5
 
 type adapter struct {
-	URI            string
-	iabCategoryMap map[string]string
-	hbSource       int
+	URI             string
+	iabCategoryMap  map[string]string
+	hbSource        int
+	randomGenerator randomutil.RandomGenerator
 }
 
 var maxImpsPerReq = 10
 
+// Builder builds a new instance of the AppNexus 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{
+		URI: config.Endpoint,
+		iabCategoryMap: map[string]string{
+			"1": "IAB20-3",
+			"9": "IAB5-3",
+		},
+		hbSource:        resolvePlatformID(config.PlatformID),
+		randomGenerator: randomutil.RandomNumberGenerator{},
+	}
+	return bidder, nil
+}
+
+func resolvePlatformID(platformID string) int {
+	if len(platformID) > 0 {
+		if val, err := strconv.Atoi(platformID); err == nil {
+			return val
+		}
+	}
+
+	return defaultPlatformID
+}
+
 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 
 	// appnexus adapter expects imp.displaymanagerver to be populated in openrtb2 endpoint
 	// but some SDKs will put it in imp.ext.prebid instead
-	displayManagerVer := buildDefaultDisplayManageVer(request)
+	displayManagerVer := buildDisplayManageVer(request)
 
 	var (
 		shouldGenerateAdPodId *bool
@@ -46,19 +71,26 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 
 	validImps := []openrtb2.Imp{}
 	for i := 0; i < len(request.Imp); i++ {
-		// If the preprocessing failed, the server won't be able to bid on this Imp. Delete it, and note the error.
-		memberId, shouldGenerateAdPodIdForImp, err := preprocess(&request.Imp[i], displayManagerVer)
+		appnexusExt, err := validateAndBuildAppNexusExt(&request.Imp[i])
 		if err != nil {
 			errs = append(errs, err)
 			continue
 		}
 
+		if err := buildRequestImp(&request.Imp[i], &appnexusExt, displayManagerVer); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		memberId := appnexusExt.Member
 		if memberId != "" {
 			if _, ok := memberIds[memberId]; !ok {
 				memberIds[memberId] = struct{}{}
 				uniqueMemberIds = append(uniqueMemberIds, memberId)
 			}
 		}
+
+		shouldGenerateAdPodIdForImp := appnexusExt.AdPodId
 		if shouldGenerateAdPodId == nil {
 			shouldGenerateAdPodId = &shouldGenerateAdPodIdForImp
 		} else if *shouldGenerateAdPodId != shouldGenerateAdPodIdForImp {
@@ -121,7 +153,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 	// If impressions number per pod is more than maxImpsPerReq - divide those imps to several requests but keep pod id the same
 	// If  adpodId feature disabled and impressions number per pod is more than maxImpsPerReq  - divide those imps to several requests but do not include ad pod id
 	if isVIDEO == 1 && *shouldGenerateAdPodId {
-		requests, errors := buildAdPodRequests(imps, request, reqExt, requestURI)
+		requests, errors := a.buildAdPodRequests(imps, request, reqExt, requestURI)
 		return requests, append(errs, errors...)
 	}
 
@@ -129,6 +161,97 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 	return requests, append(errs, errors...)
 }
 
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if httputil.IsResponseStatusCodeNoContent(response) {
+		return nil, nil
+	}
+
+	if err := httputil.CheckResponseStatusCodeForErrors(response); err != nil {
+		return nil, []error{err}
+	}
+
+	var appnexusResponse openrtb2.BidResponse
+	if err := json.Unmarshal(response.Body, &appnexusResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	var errs []error
+	bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+	for _, sb := range appnexusResponse.SeatBid {
+		for i := range sb.Bid {
+			bid := sb.Bid[i]
+
+			var bidExt appnexusBidExt
+			if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
+			bidType, err := getMediaTypeForBid(&bidExt)
+			if err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
+			iabCategory, found := a.findIabCategoryForBid(&bidExt)
+			if found {
+				bid.Cat = []string{iabCategory}
+			} else if len(bid.Cat) > 1 {
+				//create empty categories array to force bid to be rejected
+				bid.Cat = make([]string, 0)
+			}
+
+			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+				Bid:          &bid,
+				BidType:      bidType,
+				BidVideo:     &openrtb_ext.ExtBidPrebidVideo{Duration: bidExt.Appnexus.CreativeInfo.Video.Duration},
+				DealPriority: bidExt.Appnexus.DealPriority,
+			})
+		}
+	}
+
+	if appnexusResponse.Cur != "" {
+		bidderResponse.Currency = appnexusResponse.Cur
+	}
+
+	return bidderResponse, errs
+}
+
+func validateAndBuildAppNexusExt(imp *openrtb2.Imp) (openrtb_ext.ExtImpAppnexus, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return openrtb_ext.ExtImpAppnexus{}, err
+	}
+
+	var appnexusExt openrtb_ext.ExtImpAppnexus
+	if err := json.Unmarshal(bidderExt.Bidder, &appnexusExt); err != nil {
+		return openrtb_ext.ExtImpAppnexus{}, err
+	}
+
+	handleLegacyParams(&appnexusExt)
+
+	if err := validateAppnexusExt(&appnexusExt); err != nil {
+		return openrtb_ext.ExtImpAppnexus{}, err
+	}
+
+	return appnexusExt, nil
+}
+
+func handleLegacyParams(appnexusExt *openrtb_ext.ExtImpAppnexus) {
+	if appnexusExt.PlacementId == 0 && appnexusExt.DeprecatedPlacementId != 0 {
+		appnexusExt.PlacementId = appnexusExt.DeprecatedPlacementId
+	}
+	if appnexusExt.InvCode == "" && appnexusExt.LegacyInvCode != "" {
+		appnexusExt.InvCode = appnexusExt.LegacyInvCode
+	}
+	if appnexusExt.TrafficSourceCode == "" && appnexusExt.LegacyTrafficSourceCode != "" {
+		appnexusExt.TrafficSourceCode = appnexusExt.LegacyTrafficSourceCode
+	}
+	if appnexusExt.UsePaymentRule == nil && appnexusExt.DeprecatedUsePaymentRule != nil {
+		appnexusExt.UsePaymentRule = appnexusExt.DeprecatedUsePaymentRule
+	}
+}
+
 func groupByPods(imps []openrtb2.Imp) map[string]([]openrtb2.Imp) {
 	// find number of pods in response
 	podImps := make(map[string][]openrtb2.Imp)
@@ -187,41 +310,16 @@ func splitRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExt
 	return resArr, errs
 }
 
-// preprocess mutates the imp to get it ready to send to appnexus.
-//
-// It returns the member param, if it exists, and an error if anything went wrong during the preprocessing.
-func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, bool, error) {
-	var bidderExt adapters.ExtImpBidder
-	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
-		return "", false, err
-	}
-
-	var appnexusExt openrtb_ext.ExtImpAppnexus
-	if err := json.Unmarshal(bidderExt.Bidder, &appnexusExt); err != nil {
-		return "", false, err
-	}
-
-	// Accept legacy Appnexus parameters if we don't have modern ones
-	// Don't worry if both is set as validation rules should prevent, and this is temporary anyway.
-	if appnexusExt.PlacementId == 0 && appnexusExt.DeprecatedPlacementId != 0 {
-		appnexusExt.PlacementId = appnexusExt.DeprecatedPlacementId
-	}
-	if appnexusExt.InvCode == "" && appnexusExt.LegacyInvCode != "" {
-		appnexusExt.InvCode = appnexusExt.LegacyInvCode
-	}
-	if appnexusExt.TrafficSourceCode == "" && appnexusExt.LegacyTrafficSourceCode != "" {
-		appnexusExt.TrafficSourceCode = appnexusExt.LegacyTrafficSourceCode
-	}
-	if appnexusExt.UsePaymentRule == nil && appnexusExt.DeprecatedUsePaymentRule != nil {
-		appnexusExt.UsePaymentRule = appnexusExt.DeprecatedUsePaymentRule
-	}
-
+func validateAppnexusExt(appnexusExt *openrtb_ext.ExtImpAppnexus) error {
 	if appnexusExt.PlacementId == 0 && (appnexusExt.InvCode == "" || appnexusExt.Member == "") {
-		return "", false, &errortypes.BadInput{
+		return &errortypes.BadInput{
 			Message: "No placement or member+invcode provided",
 		}
 	}
+	return nil
+}
 
+func buildRequestImp(imp *openrtb2.Imp, appnexusExt *openrtb_ext.ExtImpAppnexus, displayManagerVer string) error {
 	if appnexusExt.InvCode != "" {
 		imp.TagID = appnexusExt.InvCode
 	}
@@ -246,8 +344,8 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, boo
 	}
 
 	// Populate imp.displaymanagerver if the SDK failed to do it.
-	if len(imp.DisplayManagerVer) == 0 && len(defaultDisplayManagerVer) > 0 {
-		imp.DisplayManagerVer = defaultDisplayManagerVer
+	if len(imp.DisplayManagerVer) == 0 && len(displayManagerVer) > 0 {
+		imp.DisplayManagerVer = displayManagerVer
 	}
 
 	impExt := appnexusImpExt{Appnexus: appnexusImpExtAppnexus{
@@ -257,12 +355,13 @@ func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, boo
 		UsePmtRule:        appnexusExt.UsePaymentRule,
 		PrivateSizes:      appnexusExt.PrivateSizes,
 	}}
+
 	var err error
 	if imp.Ext, err = json.Marshal(&impExt); err != nil {
-		return appnexusExt.Member, appnexusExt.AdPodId, err
+		return err
 	}
 
-	return appnexusExt.Member, appnexusExt.AdPodId, nil
+	return nil
 }
 
 func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string {
@@ -280,62 +379,6 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string {
 	return strings.Join(kvs, ",")
 }
 
-func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
-	if httputil.IsResponseStatusCodeNoContent(response) {
-		return nil, nil
-	}
-
-	if err := httputil.CheckResponseStatusCodeForErrors(response); err != nil {
-		return nil, []error{err}
-	}
-
-	var appnexusResponse openrtb2.BidResponse
-	if err := json.Unmarshal(response.Body, &appnexusResponse); err != nil {
-		return nil, []error{err}
-	}
-
-	var errs []error
-	bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5)
-	for _, sb := range appnexusResponse.SeatBid {
-		for i := range sb.Bid {
-			bid := sb.Bid[i]
-
-			var bidExt appnexusBidExt
-			if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
-				errs = append(errs, err)
-				continue
-			}
-
-			bidType, err := getMediaTypeForBid(&bidExt)
-			if err != nil {
-				errs = append(errs, err)
-				continue
-			}
-
-			iabCategory, found := a.findIabCategoryForBid(&bidExt)
-			if found {
-				bid.Cat = []string{iabCategory}
-			} else if len(bid.Cat) > 1 {
-				//create empty categories array to force bid to be rejected
-				bid.Cat = make([]string, 0)
-			}
-
-			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
-				Bid:          &bid,
-				BidType:      bidType,
-				BidVideo:     &openrtb_ext.ExtBidPrebidVideo{Duration: bidExt.Appnexus.CreativeInfo.Video.Duration},
-				DealPriority: bidExt.Appnexus.DealPriority,
-			})
-		}
-	}
-
-	if appnexusResponse.Cur != "" {
-		bidderResponse.Currency = appnexusResponse.Cur
-	}
-
-	return bidderResponse, errs
-}
-
 // getMediaTypeForBid determines which type of bid.
 func getMediaTypeForBid(bid *appnexusBidExt) (openrtb_ext.BidType, error) {
 	switch bid.Appnexus.BidType {
@@ -367,30 +410,7 @@ func appendMemberId(uri string, memberId string) string {
 	return uri + "?member_id=" + memberId
 }
 
-// Builder builds a new instance of the AppNexus 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{
-		URI: config.Endpoint,
-		iabCategoryMap: map[string]string{
-			"1": "IAB20-3",
-			"9": "IAB5-3",
-		},
-		hbSource: resolvePlatformID(config.PlatformID),
-	}
-	return bidder, nil
-}
-
-func resolvePlatformID(platformID string) int {
-	if len(platformID) > 0 {
-		if val, err := strconv.Atoi(platformID); err == nil {
-			return val
-		}
-	}
-
-	return defaultPlatformID
-}
-
-func buildDefaultDisplayManageVer(req *openrtb2.BidRequest) string {
+func buildDisplayManageVer(req *openrtb2.BidRequest) string {
 	if req.App == nil {
 		return ""
 	}
@@ -408,12 +428,12 @@ func buildDefaultDisplayManageVer(req *openrtb2.BidRequest) string {
 	return fmt.Sprintf("%s-%s", source, version)
 }
 
-func buildAdPodRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExtension appnexusReqExt, uri string) ([]*adapters.RequestData, []error) {
+func (a *adapter) buildAdPodRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExtension appnexusReqExt, uri string) ([]*adapters.RequestData, []error) {
 	var errs []error
 	podImps := groupByPods(imps)
 	requests := make([]*adapters.RequestData, 0, len(podImps))
 	for _, podImps := range podImps {
-		requestExtension.Appnexus.AdPodId = fmt.Sprint(rand.Int63())
+		requestExtension.Appnexus.AdPodId = fmt.Sprint(a.randomGenerator.GenerateInt63())
 
 		reqs, errors := splitRequests(podImps, request, requestExtension, uri)
 		requests = append(requests, reqs...)
diff --git a/adapters/pwbid/pwbid.go b/adapters/pwbid/pwbid.go
new file mode 100644
index 00000000..bcb8c137
--- /dev/null
+++ b/adapters/pwbid/pwbid.go
@@ -0,0 +1,97 @@
+package pwbid
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"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/openrtb_ext"
+	"github.com/prebid/prebid-server/util/httputil"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestData := &adapters.RequestData{
+		Method: "POST",
+		Uri:    a.endpoint,
+		Body:   requestJSON,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errors []error
+
+	if httputil.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := httputil.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+
+	for _, seatBid := range response.SeatBid {
+		for i, bid := range seatBid.Bid {
+			bidType, typeErr := getMediaTypeForBid(request.Imp, bid)
+			if typeErr != nil {
+				errors = append(errors, typeErr)
+				continue
+			}
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+
+	return bidResponse, errors
+}
+
+func getMediaTypeForBid(impressions []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	for _, impression := range impressions {
+		if impression.ID == bid.ImpID {
+			if impression.Banner != nil {
+				return openrtb_ext.BidTypeBanner, nil
+			}
+			if impression.Native != nil {
+				return openrtb_ext.BidTypeNative, nil
+			}
+			if impression.Video != nil {
+				return openrtb_ext.BidTypeVideo, nil
+			}
+		}
+	}
+
+	return "", &errortypes.BadServerResponse{
+		Message: fmt.Sprintf("The impression with ID %s is not present into the request", bid.ImpID),
+	}
+}
diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go
index 881e6120..6546842a 100644
--- a/adapters/taboola/taboola.go
+++ b/adapters/taboola/taboola.go
@@ -11,13 +11,14 @@ import (
 	"github.com/prebid/prebid-server/macros"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"net/http"
-	"net/url"
+	"strconv"
+	"strings"
 	"text/template"
 )
 
 type adapter struct {
 	endpoint *template.Template
-	hostName string
+	gvlID    string
 }
 
 // Builder builds a new instance of Taboola adapter for the given bidder with the given config.
@@ -27,20 +28,14 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
 	}
 
-	hostName := ""
-	if server.ExternalUrl != "" {
-		parsedUrl, err := url.Parse(server.ExternalUrl)
-		if err == nil && parsedUrl != nil {
-			hostName = parsedUrl.Host
-		}
+	gvlID := ""
+	if server.GvlID > 0 {
+		gvlID = strconv.Itoa(server.GvlID)
 	}
 
-	if err != nil {
-		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
-	}
 	bidder := &adapter{
 		endpoint: endpointTemplate,
-		hostName: hostName,
+		gvlID:    gvlID,
 	}
 	return bidder, nil
 }
@@ -98,6 +93,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R
 	for _, seatBid := range response.SeatBid {
 		for i := range seatBid.Bid {
 			bidType, err := getMediaType(seatBid.Bid[i].ImpID, request.Imp)
+			resolveMacros(&seatBid.Bid[i])
 			if err != nil {
 				errs = append(errs, err)
 				continue
@@ -149,7 +145,7 @@ func (a *adapter) buildRequest(request *openrtb2.BidRequest) (*adapters.RequestD
 
 // Builds endpoint url based on adapter-specific pub settings from imp.ext
 func (a *adapter) buildEndpointURL(publisherId string, mediaType string) (string, error) {
-	endpointParams := macros.EndpointTemplateParams{PublisherID: publisherId, MediaType: mediaType, Host: a.hostName}
+	endpointParams := macros.EndpointTemplateParams{PublisherID: publisherId, MediaType: mediaType, GvlID: a.gvlID}
 	resolvedUrl, err := macros.ResolveMacros(a.endpoint, endpointParams)
 	if err != nil {
 		return "", err
@@ -176,10 +172,15 @@ func createTaboolaRequests(request *openrtb2.BidRequest) (taboolaRequests []*ope
 			errs = append(errs, err)
 			continue
 		}
-		if taboolaExt.TagId != "" {
-			imp.TagID = taboolaExt.TagId
-			modifiedRequest.Imp[i] = imp
+
+		tagId := taboolaExt.TagID
+		if len(taboolaExt.TagID) < 1 {
+			tagId = taboolaExt.TagId
 		}
+
+		imp.TagID = tagId
+		modifiedRequest.Imp[i] = imp
+
 		if taboolaExt.BidFloor != 0 {
 			imp.BidFloor = taboolaExt.BidFloor
 			modifiedRequest.Imp[i] = imp
@@ -288,3 +289,11 @@ func overrideBidRequestImp(originBidRequest *openrtb2.BidRequest, imp []openrtb2
 	bidRequestResult.Imp = imp
 	return &bidRequestResult
 }
+
+func resolveMacros(bid *openrtb2.Bid) {
+	if bid != nil {
+		price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
+		bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
+		bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
+	}
+}
diff --git a/adservertargeting/adservertargeting.go b/adservertargeting/adservertargeting.go
new file mode 100644
index 00000000..6df470ec
--- /dev/null
+++ b/adservertargeting/adservertargeting.go
@@ -0,0 +1,140 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"github.com/buger/jsonparser"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"net/url"
+	"strings"
+)
+
+type DataSource string
+
+const (
+	SourceBidRequest  DataSource = "bidrequest"
+	SourceStatic      DataSource = "static"
+	SourceBidResponse DataSource = "bidresponse"
+)
+
+const (
+	bidderMacro   = "{{BIDDER}}"
+	pathDelimiter = "."
+)
+
+var (
+	allowedTypes = []jsonparser.ValueType{jsonparser.String, jsonparser.Number}
+)
+
+// RequestTargetingData struct to hold pre-processed ad server targeting keys and values
+type RequestTargetingData struct {
+	SingleVal             json.RawMessage
+	TargetingValueByImpId map[string][]byte
+}
+
+type ResponseTargetingData struct {
+	Key      string
+	HasMacro bool
+	Path     string
+}
+
+type adServerTargetingData struct {
+	RequestTargetingData  map[string]RequestTargetingData
+	ResponseTargetingData []ResponseTargetingData
+}
+
+func Apply(
+	reqWrapper *openrtb_ext.RequestWrapper,
+	resolvedRequest json.RawMessage,
+	response *openrtb2.BidResponse,
+	queryParams url.Values,
+	bidResponseExt *openrtb_ext.ExtBidResponse,
+	truncateTargetAttribute *int) *openrtb2.BidResponse {
+
+	adServerTargeting, warnings := collect(reqWrapper, resolvedRequest, queryParams)
+	response, warnings = resolve(adServerTargeting, response, warnings, truncateTargetAttribute)
+
+	if len(warnings) > 0 {
+		bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], warnings...)
+	}
+	return response
+}
+
+// collect gathers targeting keys and values from request based on initial config
+// and optimizes future key and value that should be collected from response
+func collect(
+	reqWrapper *openrtb_ext.RequestWrapper, resolvedRequest json.RawMessage,
+	queryParams url.Values) (*adServerTargetingData, []openrtb_ext.ExtBidderMessage) {
+
+	var warnings []openrtb_ext.ExtBidderMessage
+
+	adServerTargeting, err := getAdServerTargeting(reqWrapper)
+	if err != nil {
+		warnings = append(warnings, createWarning("unable to extract adServerTargeting from request"))
+		return nil, warnings
+	}
+	adServerTargeting, validationWarnings := validateAdServerTargeting(adServerTargeting)
+	if len(validationWarnings) > 0 {
+		warnings = append(warnings, validationWarnings...)
+	}
+
+	requestTargetingData := map[string]RequestTargetingData{}
+	responseTargetingData := []ResponseTargetingData{}
+
+	impsCache := requestCache{resolvedReq: resolvedRequest}
+
+	for _, targetingObj := range adServerTargeting {
+		source := strings.ToLower(targetingObj.Source)
+		switch DataSource(source) {
+		case SourceBidRequest:
+			//causes PBS to treat 'value' as a path to pull from the request object
+			value, err := getValueFromBidRequest(&impsCache, targetingObj.Value, queryParams)
+			if err != nil {
+				warnings = append(warnings, createWarning(err.Error()))
+			} else {
+				requestTargetingData[targetingObj.Key] = value
+			}
+		case SourceStatic:
+			// causes PBS to just use the 'value' provided
+			staticValue := RequestTargetingData{SingleVal: json.RawMessage(targetingObj.Value)}
+			requestTargetingData[targetingObj.Key] = staticValue
+		case SourceBidResponse:
+			//causes PBS to treat 'value' as a path to pull from the bidder's response object, specifically seatbid[j].bid[k]
+			bidResponseTargeting := ResponseTargetingData{}
+			bidResponseTargeting.Key = targetingObj.Key
+			bidResponseTargeting.Path = targetingObj.Value
+			bidResponseTargeting.HasMacro = strings.Contains(strings.ToUpper(targetingObj.Key), bidderMacro)
+			responseTargetingData = append(responseTargetingData, bidResponseTargeting)
+		}
+	}
+
+	adServerTargetingData := &adServerTargetingData{
+		RequestTargetingData:  requestTargetingData,
+		ResponseTargetingData: responseTargetingData,
+	}
+
+	return adServerTargetingData, warnings
+}
+
+func resolve(
+	adServerTargetingData *adServerTargetingData,
+	response *openrtb2.BidResponse,
+	warnings []openrtb_ext.ExtBidderMessage,
+	truncateTargetAttribute *int) (*openrtb2.BidResponse, []openrtb_ext.ExtBidderMessage) {
+
+	bidCache := bidsCache{bids: make(map[string]map[string][]byte)}
+
+	for _, seat := range response.SeatBid {
+		bidderName := seat.Seat
+		for i, bid := range seat.Bid {
+			targetingData := make(map[string]string)
+			processRequestTargetingData(adServerTargetingData, targetingData, bid.ImpID)
+			respWarnings := processResponseTargetingData(adServerTargetingData, targetingData, bidderName, bid, bidCache, response, seat.Ext)
+			if len(respWarnings) > 0 {
+				warnings = append(warnings, respWarnings...)
+			}
+			seat.Bid[i].Ext = buildBidExt(targetingData, bid, warnings, truncateTargetAttribute)
+		}
+	}
+	return response, warnings
+}
diff --git a/adservertargeting/reqcache.go b/adservertargeting/reqcache.go
new file mode 100644
index 00000000..23886d45
--- /dev/null
+++ b/adservertargeting/reqcache.go
@@ -0,0 +1,57 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"github.com/buger/jsonparser"
+	"github.com/prebid/openrtb/v17/openrtb2"
+)
+
+type requestCache struct {
+	resolvedReq json.RawMessage
+	impsData    []json.RawMessage
+}
+
+func (reqImpCache *requestCache) GetReqJson() []byte {
+	return reqImpCache.resolvedReq
+}
+
+func (reqImpCache *requestCache) GetImpsData() ([]json.RawMessage, error) {
+	if len(reqImpCache.impsData) == 0 {
+		imps, _, _, err := jsonparser.Get(reqImpCache.resolvedReq, "imp")
+		if err != nil {
+			return nil, err
+		}
+		var impsData []json.RawMessage
+
+		err = json.Unmarshal(imps, &impsData)
+		if err != nil {
+			return nil, err
+		}
+		reqImpCache.impsData = impsData
+	}
+	return reqImpCache.impsData, nil
+}
+
+type bidsCache struct {
+	// bidder name is another layer to avoid collisions in case bid ids from different bidders will be the same
+	// map[bidder name] map [bid id] bid data
+	bids map[string]map[string][]byte
+}
+
+func (bidsCache *bidsCache) GetBid(bidderName, bidId string, bid openrtb2.Bid) ([]byte, error) {
+
+	_, seatBidExists := bidsCache.bids[bidderName]
+	if !seatBidExists {
+		impToBid := make(map[string][]byte, 0)
+		bidsCache.bids[bidderName] = impToBid
+	}
+	_, bidExists := bidsCache.bids[bidderName][bidId]
+	if !bidExists {
+		bidBytes, err := json.Marshal(bid)
+		if err != nil {
+			return nil, err
+		}
+		bidsCache.bids[bidderName][bidId] = bidBytes
+	}
+	return bidsCache.bids[bidderName][bidId], nil
+}
diff --git a/adservertargeting/requestlookup.go b/adservertargeting/requestlookup.go
new file mode 100644
index 00000000..debf1983
--- /dev/null
+++ b/adservertargeting/requestlookup.go
@@ -0,0 +1,137 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/buger/jsonparser"
+	"github.com/pkg/errors"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"net/url"
+	"strings"
+)
+
+func getAdServerTargeting(reqWrapper *openrtb_ext.RequestWrapper) ([]openrtb_ext.AdServerTarget, error) {
+	reqExt, err := reqWrapper.GetRequestExt()
+	if err != nil {
+		return nil, err
+	}
+
+	reqExtPrebid := reqExt.GetPrebid()
+	if reqExtPrebid == nil {
+		return nil, nil
+	}
+
+	return reqExtPrebid.AdServerTargeting, nil
+}
+
+func validateAdServerTargeting(adServerTargeting []openrtb_ext.AdServerTarget) ([]openrtb_ext.AdServerTarget, []openrtb_ext.ExtBidderMessage) {
+	var validatedAdServerTargeting []openrtb_ext.AdServerTarget
+	var warnings []openrtb_ext.ExtBidderMessage
+	for i, targetingObj := range adServerTargeting {
+
+		isDataCorrect := true
+
+		if len(targetingObj.Key) == 0 {
+			isDataCorrect = false
+			warnings = append(warnings, createWarning(fmt.Sprintf("Key is empty for the ad server targeting object at index %d", i)))
+		}
+
+		if len(targetingObj.Value) == 0 {
+			isDataCorrect = false
+			warnings = append(warnings, createWarning(fmt.Sprintf("Value is empty for the ad server targeting object at index %d", i)))
+		}
+
+		targetingObjSource := DataSource(strings.ToLower(targetingObj.Source))
+		if targetingObjSource != SourceStatic &&
+			targetingObjSource != SourceBidRequest &&
+			targetingObjSource != SourceBidResponse {
+			isDataCorrect = false
+			warnings = append(warnings, createWarning(fmt.Sprintf("Incorrect source for the ad server targeting object at index %d", i)))
+		}
+
+		if isDataCorrect {
+			validatedAdServerTargeting = append(validatedAdServerTargeting, targetingObj)
+		}
+
+	}
+	return validatedAdServerTargeting, warnings
+}
+
+func getValueFromBidRequest(dataHolder *requestCache, path string, queryParams url.Values) (RequestTargetingData, error) {
+	//use the path specified in 'value' to look for data in the ortb bidrequest.
+	res := RequestTargetingData{}
+
+	//check if key points to query param from ext.prebid.amp.data
+	ampDataValue, err := getValueFromQueryParam(path, queryParams)
+	if ampDataValue != nil || err != nil {
+		res.SingleVal = ampDataValue
+		return res, err
+	}
+
+	// check if key points to imp data
+	impData, err := getValueFromImp(path, dataHolder)
+	if len(impData) > 0 || err != nil {
+		res.TargetingValueByImpId = impData
+		return res, err
+	}
+
+	// get data by key from request
+	requestValue, err := getDataFromRequestJson(path, dataHolder)
+	if requestValue != nil || err != nil {
+		res.SingleVal = requestValue
+		return res, err
+	}
+
+	return res, nil
+}
+
+func getValueFromQueryParam(path string, queryParams url.Values) (json.RawMessage, error) {
+	ampDataSplit, hasPrefix := verifyPrefixAndTrim(path, "ext.prebid.amp.data.")
+	if hasPrefix {
+		val := queryParams.Get(ampDataSplit)
+		if val != "" {
+			return json.RawMessage(val), nil
+		} else {
+			return nil, errors.Errorf("value not found for path: %s", path)
+		}
+	}
+	return nil, nil
+}
+
+func getValueFromImp(path string, dataHolder *requestCache) (map[string][]byte, error) {
+	impsDatas := make(map[string][]byte, 0)
+	impSplit, hasPrefix := verifyPrefixAndTrim(path, "imp.")
+	if hasPrefix {
+		//If imp is specified in the path, the assumption is that the specific imp[] desired corresponds
+		//to the seatbid[].bid[] we're working on. i.e. imp[].id=seatbid[].bid[].impid
+		// key points to data in imp
+		keySplit := strings.Split(impSplit, pathDelimiter)
+		impsData, err := dataHolder.GetImpsData()
+		if err != nil {
+			return nil, err
+		}
+		for _, impData := range impsData {
+			id, _, _, err := jsonparser.Get(impData, "id")
+			if err != nil {
+				return nil, err
+			}
+			value, err := typedLookup(impData, path, keySplit...)
+			if err != nil {
+				return nil, err
+			}
+			impsDatas[string(id)] = value
+		}
+	}
+	return impsDatas, nil
+}
+
+func getDataFromRequestJson(path string, dataHolder *requestCache) (json.RawMessage, error) {
+	keySplit := strings.Split(path, pathDelimiter)
+	reqJson := dataHolder.GetReqJson()
+	value, err := typedLookup(reqJson, path, keySplit...)
+
+	if err != nil {
+		return nil, err
+	}
+	return value, nil
+}
diff --git a/adservertargeting/respdataprocessor.go b/adservertargeting/respdataprocessor.go
new file mode 100644
index 00000000..ce5b197e
--- /dev/null
+++ b/adservertargeting/respdataprocessor.go
@@ -0,0 +1,174 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/pkg/errors"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	jsonpatch "gopkg.in/evanphx/json-patch.v4"
+	"strings"
+)
+
+const MaxKeyLength = 20
+
+func processRequestTargetingData(adServerTargetingData *adServerTargetingData, targetingData map[string]string, bidImpId string) {
+	for key, val := range adServerTargetingData.RequestTargetingData {
+		if len(val.SingleVal) > 0 {
+			targetingData[key] = string(val.SingleVal)
+		} else if len(val.TargetingValueByImpId) > 0 {
+			targetingData[key] = string(val.TargetingValueByImpId[bidImpId])
+		}
+	}
+}
+
+func processResponseTargetingData(
+	adServerTargetingData *adServerTargetingData,
+	targetingData map[string]string,
+	bidderName string,
+	bid openrtb2.Bid,
+	bidsHolder bidsCache,
+	response *openrtb2.BidResponse,
+	seatExt json.RawMessage) []openrtb_ext.ExtBidderMessage {
+
+	var warnings []openrtb_ext.ExtBidderMessage
+
+	for _, respTargetingData := range adServerTargetingData.ResponseTargetingData {
+		key := resolveKey(respTargetingData, bidderName)
+		path := respTargetingData.Path
+
+		var value string
+		var err error
+		pathSplit := strings.Split(path, pathDelimiter)
+
+		switch pathSplit[0] {
+
+		case "seatbid":
+			switch pathSplit[1] {
+			case "bid":
+				value, err = getValueFromSeatBidBid(path, bidsHolder, bidderName, bid)
+			case "ext":
+				value, err = getValueFromExt(path, "seatbid.ext.", seatExt)
+			}
+		case "ext":
+			value, err = getValueFromExt(path, "ext.", response.Ext)
+		default:
+			value, err = getValueFromResp(path, response)
+		}
+
+		if err != nil {
+			message := fmt.Sprintf("%s for bidder: %s, bid id: %s", err.Error(), bidderName, bid.ID)
+			warnings = append(warnings, createWarning(message))
+		} else {
+			targetingData[key] = value
+		}
+	}
+	return warnings
+}
+
+func buildBidExt(targetingData map[string]string,
+	bid openrtb2.Bid,
+	warnings []openrtb_ext.ExtBidderMessage,
+	truncateTargetAttribute *int) json.RawMessage {
+
+	targetingDataTruncated := truncateTargetingKeys(targetingData, truncateTargetAttribute)
+
+	bidExtTargetingData := openrtb_ext.ExtBid{
+		Prebid: &openrtb_ext.ExtBidPrebid{
+			Targeting: targetingDataTruncated,
+		},
+	}
+	bidExtTargeting, err := json.Marshal(bidExtTargetingData)
+	if err != nil {
+		warnings = append(warnings, createWarning(err.Error()))
+		return nil
+	}
+
+	newExt, err := jsonpatch.MergePatch(bid.Ext, bidExtTargeting)
+	if err != nil {
+		warnings = append(warnings, createWarning(err.Error()))
+		return nil
+	}
+	return newExt
+}
+
+func resolveKey(respTargetingData ResponseTargetingData, bidderName string) string {
+	key := respTargetingData.Key
+	if respTargetingData.HasMacro {
+		key = strings.Replace(respTargetingData.Key, bidderMacro, bidderName, -1)
+	}
+	return key
+}
+
+func truncateTargetingKeys(targetingData map[string]string, truncateTargetAttribute *int) map[string]string {
+	maxLength := MaxKeyLength
+	if truncateTargetAttribute != nil {
+		maxLength = *truncateTargetAttribute
+		if maxLength <= 0 {
+			maxLength = MaxKeyLength
+		}
+	}
+
+	targetingDataTruncated := make(map[string]string)
+	for key, value := range targetingData {
+		newKey := openrtb_ext.TargetingKey(key).TruncateKey(maxLength)
+		targetingDataTruncated[newKey] = value
+	}
+	return targetingDataTruncated
+}
+
+func getValueFromSeatBidBid(path string, bidsHolder bidsCache, bidderName string, bid openrtb2.Bid) (string, error) {
+	bidBytes, err := bidsHolder.GetBid(bidderName, bid.ID, bid)
+	if err != nil {
+		return "", err
+	}
+
+	bidSplit := strings.Split(path, "seatbid.bid.")
+	value, err := splitAndGet(bidSplit[1], bidBytes, pathDelimiter)
+	if err != nil {
+		return "", err
+
+	}
+	return value, nil
+}
+
+func getValueFromExt(path, separator string, respExt json.RawMessage) (string, error) {
+	//path points to resp.ext, means path starts with ext
+	extSplit := strings.Split(path, separator)
+	value, err := splitAndGet(extSplit[1], respExt, pathDelimiter)
+	if err != nil {
+		return "", err
+	}
+
+	return value, nil
+}
+
+// getValueFromResp optimizes retrieval of paths for a bid response (not a seat or ext)
+func getValueFromResp(path string, response *openrtb2.BidResponse) (string, error) {
+	value, err := getRespData(response, path)
+	if err != nil {
+		return "", err
+	}
+	return value, nil
+}
+
+func getRespData(bidderResp *openrtb2.BidResponse, field string) (string, error) {
+	// this code should be modified if there are changes in OpenRtb format
+	// it's up to date with OpenRTB 2.5 and 2.6
+	switch field {
+	case "id":
+		return bidderResp.ID, nil
+	case "bidid":
+		return bidderResp.BidID, nil
+	case "cur":
+		return bidderResp.Cur, nil
+	case "customdata":
+		return bidderResp.CustomData, nil
+	case "nbr":
+		return fmt.Sprint(bidderResp.NBR.Val()), nil
+
+	default:
+		return "", errors.Errorf("key not found for field in bid response: %s", field)
+	}
+
+}
diff --git a/adservertargeting/utils.go b/adservertargeting/utils.go
new file mode 100644
index 00000000..8093a4b6
--- /dev/null
+++ b/adservertargeting/utils.go
@@ -0,0 +1,58 @@
+package adservertargeting
+
+import (
+	"github.com/buger/jsonparser"
+	"github.com/pkg/errors"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"strings"
+)
+
+func splitAndGet(path string, data []byte, delimiter string) (string, error) {
+	keySplit := strings.Split(path, delimiter)
+
+	value, err := typedLookup(data, path, keySplit...)
+	if err != nil {
+		return "", err
+	}
+
+	return string(value), nil
+}
+
+func createWarning(message string) openrtb_ext.ExtBidderMessage {
+	return openrtb_ext.ExtBidderMessage{
+		Code:    errortypes.AdServerTargetingWarningCode,
+		Message: message,
+	}
+}
+
+func verifyPrefixAndTrim(path, prefix string) (string, bool) {
+	ampDataSplit := strings.Split(path, prefix)
+	if len(ampDataSplit) == 2 && ampDataSplit[0] == "" {
+		return ampDataSplit[1], true
+	}
+	return "", false
+}
+
+func typedLookup(data []byte, path string, keys ...string) ([]byte, error) {
+	value, dataType, _, err := jsonparser.Get(data, keys...)
+	if err != nil && err != jsonparser.KeyPathNotFoundError {
+		return nil, err
+	} else if err != nil && err == jsonparser.KeyPathNotFoundError {
+		return nil, errors.Errorf("value not found for path: %s", path)
+	}
+	if verifyType(dataType) {
+		return value, nil
+	}
+	return nil, errors.Errorf("incorrect value type for path: %s, value can only be string or number", path)
+}
+
+func verifyType(dataType jsonparser.ValueType) bool {
+	typeAllowed := false
+	for _, allowedType := range allowedTypes {
+		if dataType == allowedType {
+			return true
+		}
+	}
+	return typeAllowed
+}
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 8c7e3526..5554d41a 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -80,8 +80,6 @@ func NewAmpEndpoint(
 		IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed,
 	}
 
-	hookExecutor := hookexecution.NewHookExecutor(hookExecutionPlanBuilder, hookexecution.EndpointAmp, metricsEngine)
-
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
@@ -100,7 +98,7 @@ func NewAmpEndpoint(
 		nil,
 		ipValidator,
 		storedRespFetcher,
-		hookExecutor}).AmpAuction), nil
+		hookExecutionPlanBuilder}).AmpAuction), nil
 
 }
 
@@ -113,6 +111,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	// to compute the auction timeout.
 	start := time.Now()
 
+	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAmp, deps.metricsEngine)
+
 	ao := analytics.AmpObject{
 		Status:    http.StatusOK,
 		Errors:    make([]error, 0),
@@ -150,13 +150,13 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
 
 	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
-	_, rejectErr := deps.hookExecutor.ExecuteEntrypointStage(r, nilBody)
+	_, rejectErr := hookExecutor.ExecuteEntrypointStage(r, nilBody)
 	reqWrapper, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errL := deps.parseAmpRequest(r)
 	ao.Errors = append(ao.Errors, errL...)
 	// Process reject after parsing amp request, so we can use reqWrapper.
 	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
 	if rejectErr != nil {
-		labels, ao = rejectAmpRequest(*rejectErr, w, deps.hookExecutor, reqWrapper, nil, labels, ao, nil)
+		labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, nil, labels, ao, nil)
 		return
 	}
 
@@ -233,7 +233,8 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		StoredBidResponses:         storedBidResponses,
 		BidderImpReplaceImpID:      bidderImpReplaceImp,
 		PubID:                      labels.PubID,
-		HookExecutor:               deps.hookExecutor,
+		HookExecutor:               hookExecutor,
+		QueryParams:                r.URL.Query(),
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
@@ -260,11 +261,11 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	}
 
 	if isRejectErr {
-		labels, ao = rejectAmpRequest(*rejectErr, w, deps.hookExecutor, reqWrapper, account, labels, ao, errL)
+		labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, account, labels, ao, errL)
 		return
 	}
 
-	labels, ao = sendAmpResponse(w, deps.hookExecutor, response, reqWrapper, account, labels, ao, errL)
+	labels, ao = sendAmpResponse(w, hookExecutor, response, reqWrapper, account, labels, ao, errL)
 }
 
 func rejectAmpRequest(
@@ -589,15 +590,16 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2
 		req.TMax = int64(*ampParams.Timeout) - deps.cfg.AMPTimeoutAdjustment
 	}
 
-	if err := setTargeting(req, ampParams.Targeting); err != nil {
-		return []error{err}
+	var errors []error
+	if warn := setTargeting(req, ampParams.Targeting); warn != nil {
+		errors = append(errors, warn)
 	}
 
 	if err := setTrace(req, ampParams.Trace); err != nil {
-		return []error{err}
+		return append(errors, err)
 	}
 
-	return nil
+	return errors
 }
 
 // setConsentedProviders sets the addtl_consent value to user.ext.ConsentedProvidersSettings.consented_providers
@@ -651,7 +653,12 @@ func setTargeting(req *openrtb2.BidRequest, targeting string) error {
 	if len(req.Imp[0].Ext) > 0 {
 		newImpExt, err := jsonpatch.MergePatch(req.Imp[0].Ext, targetingData)
 		if err != nil {
-			return fmt.Errorf("unable to merge imp.ext with targeting data, check targeting data is correct: %s", err.Error())
+			warn := errortypes.Warning{
+				WarningCode: errortypes.BadInputErrorCode,
+				Message:     fmt.Sprintf("unable to merge imp.ext with targeting data, check targeting data is correct: %s", err.Error()),
+			}
+
+			return &warn
 		}
 		req.Imp[0].Ext = newImpExt
 		return nil
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 137b66c5..350edb76 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -99,8 +99,6 @@ func NewEndpoint(
 		IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed,
 	}
 
-	hookExecutor := hookexecution.NewHookExecutor(hookExecutionPlanBuilder, hookexecution.EndpointAuction, metricsEngine)
-
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
@@ -119,7 +117,7 @@ func NewEndpoint(
 		nil,
 		ipValidator,
 		storedRespFetcher,
-		hookExecutor}).Auction), nil
+		hookExecutionPlanBuilder}).Auction), nil
 }
 
 type endpointDeps struct {
@@ -140,7 +138,7 @@ type endpointDeps struct {
 	debugLogRegexp            *regexp.Regexp
 	privateNetworkIPValidator iputil.IPValidator
 	storedRespFetcher         stored_requests.Fetcher
-	hookExecutor              hookexecution.HookStageExecutor
+	hookExecutionPlanBuilder  hooks.ExecutionPlanBuilder
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -152,6 +150,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	// to compute the auction timeout.
 	start := time.Now()
 
+	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 	ao := analytics.AuctionObject{
 		Status:    http.StatusOK,
 		Errors:    make([]error, 0),
@@ -173,13 +173,13 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
 
-	req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels)
+	req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels, hookExecutor)
 	if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
 		return
 	}
 
 	if rejectErr := hookexecution.FindFirstRejectOrNil(errL); rejectErr != nil {
-		labels, ao = rejectAuctionRequest(*rejectErr, w, deps.hookExecutor, req.BidRequest, account, labels, ao)
+		labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao)
 		return
 	}
 
@@ -226,7 +226,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		StoredBidResponses:         storedBidResponses,
 		BidderImpReplaceImpID:      bidderImpReplaceImp,
 		PubID:                      labels.PubID,
-		HookExecutor:               deps.hookExecutor,
+		HookExecutor:               hookExecutor,
 	}
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
 	ao.Request = req.BidRequest
@@ -246,11 +246,11 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		ao.Errors = append(ao.Errors, err)
 		return
 	} else if isRejectErr {
-		labels, ao = rejectAuctionRequest(*rejectErr, w, deps.hookExecutor, req.BidRequest, account, labels, ao)
+		labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao)
 		return
 	}
 
-	labels, ao = sendAuctionResponse(w, deps.hookExecutor, response, req.BidRequest, account, labels, ao)
+	labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao)
 }
 
 func rejectAuctionRequest(
@@ -329,7 +329,7 @@ func sendAuctionResponse(
 // possible, it will return errors with messages that suggest improvements.
 //
 // If the errors list has at least one element, then no guarantees are made about the returned request.
-func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) {
+func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels, hookExecutor hookexecution.HookStageExecutor) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) {
 	req = &openrtb_ext.RequestWrapper{}
 	req.BidRequest = &openrtb2.BidRequest{}
 	errs = nil
@@ -352,7 +352,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 		}
 	}
 
-	requestJson, rejectErr := deps.hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson)
+	requestJson, rejectErr := hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson)
 	if rejectErr != nil {
 		errs = []error{rejectErr}
 		if err = json.Unmarshal(requestJson, req.BidRequest); err != nil {
@@ -395,8 +395,8 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 		return
 	}
 
-	deps.hookExecutor.SetAccount(account)
-	requestJson, rejectErr = deps.hookExecutor.ExecuteRawAuctionStage(requestJson)
+	hookExecutor.SetAccount(account)
+	requestJson, rejectErr = hookExecutor.ExecuteRawAuctionStage(requestJson)
 	if rejectErr != nil {
 		errs = []error{rejectErr}
 		if err = json.Unmarshal(requestJson, req.BidRequest); err != nil {
@@ -406,7 +406,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 	}
 
 	// retrieve storedRequests and storedImps once more in case stored data was changed by the raw auction hook
-	if hasPayloadUpdatesAt(hooks.StageRawAuctionRequest.String(), deps.hookExecutor.GetOutcomes()) {
+	if hasPayloadUpdatesAt(hooks.StageRawAuctionRequest.String(), hookExecutor.GetOutcomes()) {
 		impInfo, errs = parseImpInfo(requestJson)
 		if len(errs) > 0 {
 			return nil, nil, nil, nil, nil, nil, errs
diff --git a/endpoints/openrtb2/sample-requests/adservertargeting/all-invalid.json b/endpoints/openrtb2/sample-requests/adservertargeting/all-invalid.json
new file mode 100644
index 00000000..f5958932
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/adservertargeting/all-invalid.json
@@ -0,0 +1,190 @@
+{
+  "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "content": {
+        "episode": 6,
+        "title": "episodeName",
+        "series": "TvName",
+        "season": "season3",
+        "len": 900
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "gdpr-consent-string",
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "override-appnexus-id-in-cookie"
+          }
+        }
+      }
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1NYN"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "maxduration": 30,
+          "protocols": [
+            2,
+            3
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883452
+          },
+          "rubicon": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "aliases": {
+          "districtm": "appnexus"
+        },
+        "bidadjustmentfactors": {
+          "appnexus": 1.01,
+          "districtm": 0.98,
+          "rubicon": 0.99
+        },
+        "cache": {
+          "bids": {}
+        },
+        "channel": {
+          "name": "video",
+          "version": "1.0"
+        },
+        "adservertargeting": [
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "incorrect",
+            "value": "imp.video.mimes"
+          },
+          {
+            "key": "",
+            "source": "bidrequest",
+            "value": "imp.video"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "",
+            "value": "site.content"
+          },
+          {
+            "key": "adt_{{BIDDER}}_impid",
+            "source": "bidresponse",
+            "value": ""
+          }
+        ]
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 1.01,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 0.99,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0,
+    "ext": {
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          },
+          {
+            "code": 10007,
+            "message": "Incorrect source for the ad server targeting object at index 0"
+          },
+          {
+            "code": 10007,
+            "message": "Key is empty for the ad server targeting object at index 1"
+          },
+          {
+            "code": 10007,
+            "message": "Incorrect source for the ad server targeting object at index 2"
+          },
+          {
+            "code": 10007,
+            "message": "Value is empty for the ad server targeting object at index 3"
+          }
+        ]
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/adservertargeting/all-valid.json b/endpoints/openrtb2/sample-requests/adservertargeting/all-valid.json
new file mode 100644
index 00000000..80f66f46
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/adservertargeting/all-valid.json
@@ -0,0 +1,197 @@
+{
+  "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "content": {
+        "episode": 6,
+        "title": "episodeName",
+        "series": "TvName",
+        "season": "season3",
+        "len": 900
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "gdpr-consent-string",
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "override-appnexus-id-in-cookie"
+          }
+        }
+      }
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1NYN"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "maxduration": 30,
+          "protocols": [
+            2,
+            3
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883452
+          },
+          "rubicon": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "aliases": {
+          "districtm": "appnexus"
+        },
+        "bidadjustmentfactors": {
+          "appnexus": 1.01,
+          "districtm": 0.98,
+          "rubicon": 0.99
+        },
+        "cache": {
+          "bids": {}
+        },
+        "channel": {
+          "name": "video",
+          "version": "1.0"
+        },
+        "adservertargeting": [
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "bidrequest",
+            "value": "imp.video.maxduration"
+          },
+          {
+            "key": "adt_hb_req_imp_w",
+            "source": "bidrequest",
+            "value": "imp.video.w"
+          },
+          {
+            "key": "adt_hb_req_imp_h",
+            "source": "bidrequest",
+            "value": "imp.video.h"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "bidrequest",
+            "value": "site.content.season"
+          },
+          {
+            "key": "adt_hb_static_thing",
+            "source": "static",
+            "value": "test-static-value"
+          },
+          {
+            "key": "adt_{{BIDDER}}_impid",
+            "source": "bidresponse",
+            "value": "seatbid.bid.impid"
+          },
+          {
+            "key": "adt_custom2",
+            "source": "bidresponse",
+            "value": "seatbid.bid.price"
+          }
+        ]
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 1.01,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "targeting": {
+                  "adt_appnexus_impid": "some-impression-id",
+                  "adt_custom2": "1.01",
+                  "adt_hb_req_imp_h": "480",
+                  "adt_hb_req_imp_maxdu": "30",
+                  "adt_hb_req_imp_w": "640",
+                  "adt_hb_req_site_cont": "season3",
+                  "adt_hb_static_thing": "test-static-value"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 0.99,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "targeting": {
+                  "adt_custom2": "0.99",
+                  "adt_hb_req_imp_h": "480",
+                  "adt_hb_req_imp_maxdu": "30",
+                  "adt_hb_req_imp_w": "640",
+                  "adt_hb_req_site_cont": "season3",
+                  "adt_hb_static_thing": "test-static-value",
+                  "adt_rubicon_impid": "some-impression-id"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/adservertargeting/all-warnings.json b/endpoints/openrtb2/sample-requests/adservertargeting/all-warnings.json
new file mode 100644
index 00000000..95dc0ebe
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/adservertargeting/all-warnings.json
@@ -0,0 +1,207 @@
+{
+  "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "content": {
+        "episode": 6,
+        "title": "episodeName",
+        "series": "TvName",
+        "season": "season3",
+        "len": 900
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "gdpr-consent-string",
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "override-appnexus-id-in-cookie"
+          }
+        }
+      }
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1NYN"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "maxduration": 30,
+          "protocols": [
+            2,
+            3
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883452
+          },
+          "rubicon": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "aliases": {
+          "districtm": "appnexus"
+        },
+        "bidadjustmentfactors": {
+          "appnexus": 1.01,
+          "districtm": 0.98,
+          "rubicon": 0.99
+        },
+        "cache": {
+          "bids": {}
+        },
+        "channel": {
+          "name": "video",
+          "version": "1.0"
+        },
+        "adservertargeting": [
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "bidrequest",
+            "value": "imp.video.mimes"
+          },
+          {
+            "key": "adt_hb_req_imp_w",
+            "source": "bidrequest",
+            "value": "imp.video"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "bidrequest",
+            "value": "site.content"
+          },
+          {
+            "key": "adt_{{BIDDER}}_impid",
+            "source": "bidresponse",
+            "value": "seatbid.bid.ext.key"
+          },
+          {
+            "key": "adt_custom2",
+            "source": "bidresponse",
+            "value": "seatbid.bid.key"
+          }
+        ]
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 1.01,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 0.99,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0,
+    "ext": {
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: imp.video.mimes, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: imp.video, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: site.content, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "value not found for path: ext.key for bidder"
+          },
+          {
+            "code": 10007,
+            "message": "value not found for path: key for bidder"
+          },
+          {
+            "code": 10007,
+            "message": "value not found for path: ext.key for bidder"
+          },
+          {
+            "code": 10007,
+            "message": "value not found for path: key for bidder"
+          }
+        ]
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/adservertargeting/valid-and-warnings.json b/endpoints/openrtb2/sample-requests/adservertargeting/valid-and-warnings.json
new file mode 100644
index 00000000..f7b1b33d
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/adservertargeting/valid-and-warnings.json
@@ -0,0 +1,234 @@
+{
+  "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "content": {
+        "episode": 6,
+        "title": "episodeName",
+        "series": "TvName",
+        "season": "season3",
+        "len": 900
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "gdpr-consent-string",
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "override-appnexus-id-in-cookie"
+          }
+        }
+      }
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1NYN"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "maxduration": 30,
+          "protocols": [
+            2,
+            3
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883452
+          },
+          "rubicon": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "aliases": {
+          "districtm": "appnexus"
+        },
+        "bidadjustmentfactors": {
+          "appnexus": 1.01,
+          "districtm": 0.98,
+          "rubicon": 0.99
+        },
+        "cache": {
+          "bids": {}
+        },
+        "channel": {
+          "name": "video",
+          "version": "1.0"
+        },
+        "adservertargeting": [
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "bidrequest",
+            "value": "imp.video.maxduration"
+          },
+          {
+            "key": "adt_hb_req_imp_w",
+            "source": "bidrequest",
+            "value": "imp.video.w"
+          },
+          {
+            "key": "adt_hb_req_imp_h",
+            "source": "bidrequest",
+            "value": "imp.video.h"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "bidrequest",
+            "value": "site.content.season"
+          },
+          {
+            "key": "adt_hb_static_thing",
+            "source": "static",
+            "value": "test-static-value"
+          },
+          {
+            "key": "adt_{{BIDDER}}_impid",
+            "source": "bidresponse",
+            "value": "seatbid.bid.impid"
+          },
+          {
+            "key": "adt_custom2",
+            "source": "bidresponse",
+            "value": "seatbid.bid.price"
+          },
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "bidrequest",
+            "value": "imp.video.mimes"
+          },
+          {
+            "key": "adt_hb_req_imp_w",
+            "source": "bidrequest",
+            "value": "imp.video"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "bidrequest",
+            "value": "site.content"
+          }
+        ]
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 1.01,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "targeting": {
+                  "adt_appnexus_impid": "some-impression-id",
+                  "adt_custom2": "1.01",
+                  "adt_hb_req_imp_h": "480",
+                  "adt_hb_req_imp_maxdu": "30",
+                  "adt_hb_req_imp_w": "640",
+                  "adt_hb_req_site_cont": "season3",
+                  "adt_hb_static_thing": "test-static-value"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 0.99,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "targeting": {
+                  "adt_custom2": "0.99",
+                  "adt_hb_req_imp_h": "480",
+                  "adt_hb_req_imp_maxdu": "30",
+                  "adt_hb_req_imp_w": "640",
+                  "adt_hb_req_site_cont": "season3",
+                  "adt_hb_static_thing": "test-static-value",
+                  "adt_rubicon_impid": "some-impression-id"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0,
+    "ext": {
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: imp.video.mimes, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: imp.video, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: site.content, value can only be string or number"
+          }
+        ]
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/adservertargeting/valid-invalid-and-warnings.json b/endpoints/openrtb2/sample-requests/adservertargeting/valid-invalid-and-warnings.json
new file mode 100644
index 00000000..d5854de7
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/adservertargeting/valid-invalid-and-warnings.json
@@ -0,0 +1,274 @@
+{
+  "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "content": {
+        "episode": 6,
+        "title": "episodeName",
+        "series": "TvName",
+        "season": "season3",
+        "len": 900
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "gdpr-consent-string",
+        "prebid": {
+          "buyeruids": {
+            "appnexus": "override-appnexus-id-in-cookie"
+          }
+        }
+      }
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1NYN"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "maxduration": 30,
+          "protocols": [
+            2,
+            3
+          ],
+          "w": 640,
+          "h": 480
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883452
+          },
+          "rubicon": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {
+      "prebid": {
+        "aliases": {
+          "districtm": "appnexus"
+        },
+        "bidadjustmentfactors": {
+          "appnexus": 1.01,
+          "districtm": 0.98,
+          "rubicon": 0.99
+        },
+        "cache": {
+          "bids": {}
+        },
+        "channel": {
+          "name": "video",
+          "version": "1.0"
+        },
+        "adservertargeting": [
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "bidrequest",
+            "value": "imp.video.maxduration"
+          },
+          {
+            "key": "adt_hb_req_imp_w",
+            "source": "bidrequest",
+            "value": "imp.video.w"
+          },
+          {
+            "key": "adt_hb_req_imp_h",
+            "source": "bidrequest",
+            "value": "imp.video.h"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "bidrequest",
+            "value": "site.content.season"
+          },
+          {
+            "key": "adt_hb_static_thing",
+            "source": "static",
+            "value": "test-static-value"
+          },
+          {
+            "key": "adt_{{BIDDER}}_impid",
+            "source": "bidresponse",
+            "value": "seatbid.bid.impid"
+          },
+          {
+            "key": "adt_custom2",
+            "source": "bidresponse",
+            "value": "seatbid.bid.price"
+          },
+          {
+            "key": "adt_hb_req_imp_maxduration",
+            "source": "bidrequest",
+            "value": "imp.video.mimes"
+          },
+          {
+            "key": "adt_hb_req_imp_w",
+            "source": "bidrequest",
+            "value": "imp.video"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "bidrequest",
+            "value": "site.content"
+          },
+          {
+            "key": "",
+            "source": "bidrequest",
+            "value": "imp.video"
+          },
+          {
+            "key": "adt_hb_req_site_contest_param",
+            "source": "",
+            "value": "site.content"
+          },
+          {
+            "key": "adt_{{BIDDER}}_impid",
+            "source": "bidresponse",
+            "value": ""
+          },
+          {
+            "key": "adt_incorrect_bidresp",
+            "source": "bidresponse",
+            "value": "incorrect"
+          }
+        ]
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 1.01,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "targeting": {
+                  "adt_appnexus_impid": "some-impression-id",
+                  "adt_custom2": "1.01",
+                  "adt_hb_req_imp_h": "480",
+                  "adt_hb_req_imp_maxdu": "30",
+                  "adt_hb_req_imp_w": "640",
+                  "adt_hb_req_site_cont": "season3",
+                  "adt_hb_static_thing": "test-static-value"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 0.99,
+            "ext": {
+              "origbidcpm": 1,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "targeting": {
+                  "adt_custom2": "0.99",
+                  "adt_hb_req_imp_h": "480",
+                  "adt_hb_req_imp_maxdu": "30",
+                  "adt_hb_req_imp_w": "640",
+                  "adt_hb_req_site_cont": "season3",
+                  "adt_hb_static_thing": "test-static-value",
+                  "adt_rubicon_impid": "some-impression-id"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0,
+    "ext": {
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          },
+          {
+            "code": 10007,
+            "message": "Key is empty for the ad server targeting object at index 10"
+          },
+          {
+            "code": 10007,
+            "message": "Incorrect source for the ad server targeting object at index 11"
+          },
+          {
+            "code": 10007,
+            "message": "Value is empty for the ad server targeting object at index 12"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: imp.video.mimes, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: imp.video, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "incorrect value type for path: site.content, value can only be string or number"
+          },
+          {
+            "code": 10007,
+            "message": "key not found for field in bid response: incorrect for bidder"
+          },
+          {
+            "code": 10007,
+            "message": "key not found for field in bid response: incorrect for bidder"
+          }
+        ]
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/hooks/auction.json b/endpoints/openrtb2/sample-requests/hooks/auction.json
index a2dc92c2..76114689 100644
--- a/endpoints/openrtb2/sample-requests/hooks/auction.json
+++ b/endpoints/openrtb2/sample-requests/hooks/auction.json
@@ -47,8 +47,7 @@
           {
             "id": "appnexus-bid",
             "impid": "some-impression-id",
-            "price": 0,
-            "ext": {"prebid":{"type":"banner"}}
+            "price": 0
           }
         ],
         "seat": "appnexus"
diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json
index dd209763..bece4227 100644
--- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json
+++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_reject.json
@@ -51,8 +51,7 @@
           {
             "id": "applogy-bid",
             "impid": "some-impression-id",
-            "price": 0,
-            "ext": {"prebid":{"type":"banner"}}
+            "price": 0
           }
         ],
         "seat": "applogy"
diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json
index ba5c75f0..32743d67 100644
--- a/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json
+++ b/endpoints/openrtb2/sample-requests/hooks/auction_bidder_response_reject.json
@@ -51,8 +51,7 @@
           {
             "id": "applogy-bid",
             "impid": "some-impression-id",
-            "price": 0,
-            "ext": {"prebid":{"type":"banner"}}
+            "price": 0
           }
         ],
         "seat": "applogy"
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json
index eb9c8ee8..ba9079d4 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json
@@ -43,8 +43,7 @@
             {
               "id": "appnexus-bid",
               "impid": "some-impression-id",
-              "price": 0,
-              "ext": {"prebid":{"type":"banner"}}
+              "price": 0
             }
           ],
           "seat": "appnexus"
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index d27d7084..7ceb7656 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -18,6 +18,7 @@ import (
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
 	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/ortb"
 	jsonpatch "gopkg.in/evanphx/json-patch.v4"
@@ -88,7 +89,7 @@ func NewVideoEndpoint(
 		videoEndpointRegexp,
 		ipValidator,
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{}}).VideoAuctionEndpoint), nil
+		hooks.EmptyPlanBuilder{}}).VideoAuctionEndpoint), nil
 }
 
 /*
@@ -296,7 +297,6 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 
 	secGPC := r.Header.Get("Sec-GPC")
-
 	auctionRequest := exchange.AuctionRequest{
 		BidRequestWrapper:          bidReqWrapper,
 		Account:                    *account,
@@ -306,7 +306,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		LegacyLabels:               labels,
 		GlobalPrivacyControlHeader: secGPC,
 		PubID:                      labels.PubID,
-		HookExecutor:               deps.hookExecutor,
+		HookExecutor:               hookexecution.EmptyHookExecutor{},
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog)
diff --git a/errortypes/code.go b/errortypes/code.go
index e796a05d..f81ee464 100644
--- a/errortypes/code.go
+++ b/errortypes/code.go
@@ -25,6 +25,7 @@ const (
 	DisabledCurrencyConversionWarningCode
 	AlternateBidderCodeWarningCode
 	MultiBidWarningCode
+	AdServerTargetingWarningCode
 )
 
 // Coder provides an error or warning code with severity.
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index cbdaf084..77896761 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -123,6 +123,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/pubmatic"
 	"github.com/prebid/prebid-server/adapters/pubnative"
 	"github.com/prebid/prebid-server/adapters/pulsepoint"
+	"github.com/prebid/prebid-server/adapters/pwbid"
 	"github.com/prebid/prebid-server/adapters/revcontent"
 	"github.com/prebid/prebid-server/adapters/rhythmone"
 	"github.com/prebid/prebid-server/adapters/richaudience"
@@ -291,7 +292,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderNextMillennium:    nextmillennium.Builder,
 		openrtb_ext.BidderNinthDecimal:      ninthdecimal.Builder,
 		openrtb_ext.BidderNoBid:             nobid.Builder,
-		openrtb_ext.BidderOFTMedia:          adtelligent.Builder,
 		openrtb_ext.BidderOneTag:            onetag.Builder,
 		openrtb_ext.BidderOpenWeb:           openweb.Builder,
 		openrtb_ext.BidderOpenx:             openx.Builder,
@@ -303,6 +303,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderPubmatic:          pubmatic.Builder,
 		openrtb_ext.BidderPubnative:         pubnative.Builder,
 		openrtb_ext.BidderPulsepoint:        pulsepoint.Builder,
+		openrtb_ext.BidderPWBid:             pwbid.Builder,
 		openrtb_ext.BidderQuantumdex:        apacdex.Builder,
 		openrtb_ext.BidderRevcontent:        revcontent.Builder,
 		openrtb_ext.BidderRhythmone:         rhythmone.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index 544fe936..056dbf35 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -90,6 +90,7 @@ func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string
 		"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
 		"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 		"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
+		"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
 	}
 
 	for name, info := range infos {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index b23d12e1..34742358 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/adservertargeting"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/errortypes"
@@ -200,6 +201,7 @@ type AuctionRequest struct {
 	BidderImpReplaceImpID stored_responses.BidderImpReplaceImpID
 	PubID                 string
 	HookExecutor          hookexecution.StageExecutor
+	QueryParams           url.Values
 }
 
 // BidderRequest holds the bidder specific request and all other
@@ -248,7 +250,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(r.BidRequestWrapper.Test, requestExtPrebid, r.Account.DebugAllow, debugLog)
 
 	// save incoming request with stored requests (if applicable) to return in debug logs
-	if responseDebugAllow {
+	if responseDebugAllow || len(requestExtPrebid.AdServerTargeting) > 0 {
 		if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
 			return nil, err
 		}
@@ -260,7 +262,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	}
 	e.me.RecordDebugRequest(responseDebugAllow || accountDebugAllow, r.PubID)
 
-	if r.RequestType == metrics.ReqTypeORTB2Web || r.RequestType == metrics.ReqTypeORTB2App {
+	if r.RequestType == metrics.ReqTypeORTB2Web ||
+		r.RequestType == metrics.ReqTypeORTB2App ||
+		r.RequestType == metrics.ReqTypeAMP {
 		//Extract First party data for auction endpoint only
 		resolvedFPD, fpdErrors := firstpartydata.ExtractFPDForBidders(r.BidRequestWrapper)
 		if len(fpdErrors) > 0 {
@@ -436,6 +440,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 
 	// Build the response
 	bidResponse, err := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs)
+
+	bidResponse = adservertargeting.Apply(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute)
+
+	bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt)
+
 	return bidResponse, err
 }
 
@@ -828,8 +837,6 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 
 	bidResponse.SeatBid = seatBids
 
-	bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt)
-
 	return bidResponse, err
 }
 
diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go
index 6d984fc4..9a1fd341 100644
--- a/hooks/hookexecution/executor.go
+++ b/hooks/hookexecution/executor.go
@@ -314,33 +314,33 @@ func (e *hookExecutor) pushStageOutcome(outcome StageOutcome) {
 
 type EmptyHookExecutor struct{}
 
-func (executor *EmptyHookExecutor) SetAccount(_ *config.Account) {}
+func (executor EmptyHookExecutor) SetAccount(_ *config.Account) {}
 
-func (executor *EmptyHookExecutor) GetOutcomes() []StageOutcome {
+func (executor EmptyHookExecutor) GetOutcomes() []StageOutcome {
 	return []StageOutcome{}
 }
 
-func (executor *EmptyHookExecutor) ExecuteEntrypointStage(_ *http.Request, body []byte) ([]byte, *RejectError) {
+func (executor EmptyHookExecutor) ExecuteEntrypointStage(_ *http.Request, body []byte) ([]byte, *RejectError) {
 	return body, nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) {
+func (executor EmptyHookExecutor) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) {
 	return body, nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.RequestWrapper) error {
+func (executor EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.RequestWrapper) error {
 	return nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError {
+func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb2.BidRequest, bidder string) *RejectError {
 	return nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.BidderResponse, _ string) *RejectError {
+func (executor EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.BidderResponse, _ string) *RejectError {
 	return nil
 }
 
-func (executor *EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
+func (executor EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
 }
 
-func (executor *EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {}
+func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {}
diff --git a/macros/macros.go b/macros/macros.go
index b265e4cd..7292e0c8 100644
--- a/macros/macros.go
+++ b/macros/macros.go
@@ -14,6 +14,7 @@ type EndpointTemplateParams struct {
 	AccountID   string
 	AdUnit      string
 	MediaType   string
+	GvlID       string
 }
 
 // UserSyncTemplateParams specifies params for an user sync URL template
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index 8bd2aca7..b242258e 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -20,7 +20,7 @@ type ExtBidPrebid struct {
 	Meta              *ExtBidPrebidMeta   `json:"meta,omitempty"`
 	Targeting         map[string]string   `json:"targeting,omitempty"`
 	TargetBidderCode  string              `json:"targetbiddercode,omitempty"`
-	Type              BidType             `json:"type"`
+	Type              BidType             `json:"type,omitempty"`
 	Video             *ExtBidPrebidVideo  `json:"video,omitempty"`
 	Events            *ExtBidPrebidEvents `json:"events,omitempty"`
 	BidId             string              `json:"bidid,omitempty"`
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index dafef9f5..171d31bb 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -204,7 +204,6 @@ const (
 	BidderNextMillennium    BidderName = "nextmillennium"
 	BidderNinthDecimal      BidderName = "ninthdecimal"
 	BidderNoBid             BidderName = "nobid"
-	BidderOFTMedia          BidderName = "oftmedia"
 	BidderOneTag            BidderName = "onetag"
 	BidderOpenWeb           BidderName = "openweb"
 	BidderOpenx             BidderName = "openx"
@@ -216,6 +215,7 @@ const (
 	BidderPubmatic          BidderName = "pubmatic"
 	BidderPubnative         BidderName = "pubnative"
 	BidderPulsepoint        BidderName = "pulsepoint"
+	BidderPWBid             BidderName = "pwbid"
 	BidderQuantumdex        BidderName = "quantumdex"
 	BidderRevcontent        BidderName = "revcontent"
 	BidderRhythmone         BidderName = "rhythmone"
@@ -388,7 +388,6 @@ func CoreBidderNames() []BidderName {
 		BidderNextMillennium,
 		BidderNinthDecimal,
 		BidderNoBid,
-		BidderOFTMedia,
 		BidderOneTag,
 		BidderOpenWeb,
 		BidderOpenx,
@@ -400,6 +399,7 @@ func CoreBidderNames() []BidderName {
 		BidderPubmatic,
 		BidderPubnative,
 		BidderPulsepoint,
+		BidderPWBid,
 		BidderQuantumdex,
 		BidderRevcontent,
 		BidderRhythmone,
diff --git a/openrtb_ext/imp_pwbid.go b/openrtb_ext/imp_pwbid.go
new file mode 100644
index 00000000..d3f0da05
--- /dev/null
+++ b/openrtb_ext/imp_pwbid.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ExtImpPwbid struct {
+	SiteId   string  `json:"siteId"`
+	BidFloor float32 `json:"bidFloor"`
+	IsTest   bool    `json:"isTest"`
+}
diff --git a/openrtb_ext/imp_taboola.go b/openrtb_ext/imp_taboola.go
index 40057a0a..798ee28c 100644
--- a/openrtb_ext/imp_taboola.go
+++ b/openrtb_ext/imp_taboola.go
@@ -5,6 +5,7 @@ type ImpExtTaboola struct {
 	PublisherDomain string   `json:"publisherDomain"`
 	BidFloor        float64  `json:"bidfloor"`
 	TagId           string   `json:"tagid"`
+	TagID           string   `json:"tagId"`
 	BCat            []string `json:"bcat"`
 	BAdv            []string `json:"badv"`
 	PageType        string   `json:"pageType"`
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index e82ea17b..95c3e389 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -73,6 +73,14 @@ type ExtRequestPrebid struct {
 	// - basic: excludes debugmessages and analytic_tags from output
 	// any other value or an empty string disables trace output at all.
 	Trace string `json:"trace,omitempty"`
+
+	AdServerTargeting []AdServerTarget `json:"adservertargeting,omitempty"`
+}
+
+type AdServerTarget struct {
+	Key    string `json:"key,omitempty"`
+	Source string `json:"source,omitempty"`
+	Value  string `json:"value,omitempty"`
 }
 
 // Experiment defines if experimental features are available for the request
diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml
index d1ddae74..3342b884 100644
--- a/static/bidder-info/adyoulike.yaml
+++ b/static/bidder-info/adyoulike.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4"
+endpoint: "https://hb-ss.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4"
 maintainer:
   email: "core@adyoulike.com"
 gvlVendorID: 259
diff --git a/static/bidder-info/madvertise.yaml b/static/bidder-info/madvertise.yaml
index 262987a6..f94f988a 100644
--- a/static/bidder-info/madvertise.yaml
+++ b/static/bidder-info/madvertise.yaml
@@ -1,6 +1,6 @@
 endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}"
 maintainer:
-  email: "support@madvertise.com"
+  email: "hb@azerion.com"
 gvlVendorID: 153
 capabilities:
   app:
diff --git a/static/bidder-info/oftmedia.yaml b/static/bidder-info/pwbid.yaml
similarity index 52%
rename from static/bidder-info/oftmedia.yaml
rename to static/bidder-info/pwbid.yaml
index 587a6643..ac687273 100644
--- a/static/bidder-info/oftmedia.yaml
+++ b/static/bidder-info/pwbid.yaml
@@ -1,18 +1,20 @@
-endpoint: "http://ghb.ortb.152media.info/pbs/ortb"
+endpoint: "https://bid.pubwise.io/prebid"
 maintainer:
-  email: "admin@152media.com"
-gvlVendorID: 1111
+  email: info@pubwise.io
+gvlVendorID: 842
 capabilities:
   app:
     mediaTypes:
       - banner
+      - native
       - video
   site:
     mediaTypes:
       - banner
+      - native
       - video
 userSync:
-  # oftmedia supports user syncing, but requires configuration by the host. contact this
+  # PubWise supports user syncing, but requires configuration by the host. contact this
   # bidder directly at the email address in this file to ask about enabling user sync.
   supports:
-    - iframe
+    - redirect
\ No newline at end of file
diff --git a/static/bidder-info/taboola.yaml b/static/bidder-info/taboola.yaml
index 693ef048..348165a9 100644
--- a/static/bidder-info/taboola.yaml
+++ b/static/bidder-info/taboola.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://{{.MediaType}}.bidder.taboola.com/OpenRTB/PS/auction/{{.Host}}/{{.PublisherID}}"
+endpoint: "https://{{.MediaType}}.bidder.taboola.com/OpenRTB/PS/auction/{{.GvlID}}/{{.PublisherID}}"
 maintainer:
   email:  ps-team@taboola.com
 gvlVendorID: 42
diff --git a/static/bidder-params/oftmedia.json b/static/bidder-params/oftmedia.json
deleted file mode 100644
index dba2aa7f..00000000
--- a/static/bidder-params/oftmedia.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-04/schema#",
-  "title": "OFTMedia Adapter Params",
-  "description": "A schema which validates params accepted by the OFTMedia adapter",
-
-  "type": "object",
-  "properties": {
-    "placementId": {
-      "type": "integer",
-      "description": "An ID which identifies this placement of the impression"
-    },
-    "siteId": {
-      "type": "integer",
-      "description": "An ID which identifies the site selling the impression"
-    },
-    "aid": {
-      "type": "integer",
-      "description": "An ID which identifies the channel"
-    },
-    "bidFloor": {
-      "type": "number",
-      "description": "BidFloor, US Dollars"
-    }
-  },
-  "required": ["aid"]
-}
diff --git a/static/bidder-params/pwbid.json b/static/bidder-params/pwbid.json
new file mode 100644
index 00000000..e973d156
--- /dev/null
+++ b/static/bidder-params/pwbid.json
@@ -0,0 +1,21 @@
+{
+	"$schema": "http://json-schema.org/draft-04/schema#",
+	"title": "PubWise (pwbid) Adapter Params",
+	"description": "A schema which validates params accepted by the PubWise (pwbid) adapter",
+	"type": "object",
+	"properties": {
+		"siteId": {
+			"type": "string",
+			"description": "The site ID provided by the PubWise system"
+		},
+        "bidFloor": {
+            "type": "number",
+            "description": "Value to pass as the bidfloor for this bid"
+        },
+        "isTest": {
+            "type": "boolean",
+            "description": "A boolean to indicate 100% fill test placement request"
+        }
+	},
+	"required": ["siteId"]
+}
diff --git a/static/bidder-params/taboola.json b/static/bidder-params/taboola.json
index 84bdf149..25c12660 100644
--- a/static/bidder-params/taboola.json
+++ b/static/bidder-params/taboola.json
@@ -12,7 +12,12 @@
       "type": "string"
     },
     "tagid": {
-      "type": "string"
+      "type": "string",
+      "description": "Deprecated, use tagId instead."
+    },
+    "tagId": {
+      "type": "string",
+      "description": "preferred, will get precedence if both tagId and tagid are defined"
     },
     "bidfloor": {
       "type": "number"
@@ -36,5 +41,12 @@
       "type": "integer"
     }
   },
-  "required": ["tagid","publisherId"]
+  "oneOf": [
+    {
+      "required": [ "tagid", "publisherId" ]
+    },
+    {
+      "required": [ "tagId", "publisherId" ]
+    }
+  ]
 }
diff --git a/util/randomutil/randomutil.go b/util/randomutil/randomutil.go
new file mode 100644
index 00000000..544eb1ab
--- /dev/null
+++ b/util/randomutil/randomutil.go
@@ -0,0 +1,15 @@
+package randomutil
+
+import (
+	"math/rand"
+)
+
+type RandomGenerator interface {
+	GenerateInt63() int64
+}
+
+type RandomNumberGenerator struct{}
+
+func (RandomNumberGenerator) GenerateInt63() int64 {
+	return rand.Int63()
+}


Test patch:
diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go
index 000d07e9..6797f4bb 100644
--- a/adapters/appnexus/appnexus_test.go
+++ b/adapters/appnexus/appnexus_test.go
@@ -16,6 +16,9 @@ func TestJsonSamples(t *testing.T) {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
 	}
 
+	bidderAppNexus, _ := bidder.(*adapter)
+	bidderAppNexus.randomGenerator = FakeRandomNumberGenerator{Number: 10}
+
 	adapterstest.RunJSONBidderTest(t, "appnexustest", bidder)
 }
 
@@ -26,3 +29,12 @@ func TestMemberQueryParam(t *testing.T) {
 		t.Errorf("appendMemberId() failed on URI with query string. Expected %s, got %s", expected, uriWithMember)
 	}
 }
+
+// fakerandomNumberGenerator
+type FakeRandomNumberGenerator struct {
+	Number int64
+}
+
+func (f FakeRandomNumberGenerator) GenerateInt63() int64 {
+	return f.Number
+}
diff --git a/adapters/appnexus/appnexustest/exemplary/simple-banner.json b/adapters/appnexus/appnexustest/exemplary/simple-banner.json
index 59931fb6..15a7666e 100644
--- a/adapters/appnexus/appnexustest/exemplary/simple-banner.json
+++ b/adapters/appnexus/appnexustest/exemplary/simple-banner.json
@@ -18,7 +18,8 @@
         },
         "ext": {
           "bidder": {
-            "placement_id": 1
+            "placement_id": 1,
+            "position": "above"
           }
         }
       }
@@ -52,6 +53,7 @@
                   }
                 ],
                 "w": 300,
+                "pos": 1,
                 "h": 250
               },
               "ext": {
diff --git a/adapters/appnexus/appnexustest/supplemental/legacy-params.json b/adapters/appnexus/appnexustest/supplemental/legacy-params.json
new file mode 100644
index 00000000..82031145
--- /dev/null
+++ b/adapters/appnexus/appnexustest/supplemental/legacy-params.json
@@ -0,0 +1,148 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id-1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": 1,
+            "invCode": "abc123",
+            "trafficSourceCode": "xyz",
+            "position": "above"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://ib.adnxs.com/openrtb2",
+        "body": {
+          "id": "test-request-id",
+          "ext": {
+            "appnexus": {
+              "hb_source": 5
+            },
+            "prebid": {}
+          },
+          "imp": [
+            {
+              "id": "test-imp-id-1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "pos": 1,
+                "h": 250
+              },
+              "ext": {
+                "appnexus": {
+                  "placement_id": 1,
+                  "traffic_source_code": "xyz"
+                }
+              },
+              "tagid": "abc123"
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [{
+                "id": "7706636740145184841",
+                "impid": "test-imp-id-1",
+                "price": 0.500000,
+                "adid": "29681110",
+                "adm": "some-test-ad",
+                "adomain": ["appnexus.com"],
+                "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110",
+                "cid": "958",
+                "crid": "29681110",
+                "h": 250,
+                "w": 300,
+                "cat": ["IAB20-3"],
+                "ext": {
+                  "appnexus": {
+                    "brand_id": 1,
+                    "brand_category_id": 1,
+                    "auction_id": 8189378542222915032,
+                    "bid_ad_type": 0,
+                    "bidder_id": 2,
+                    "ranking_price": 0.000000,
+                    "deal_priority": 5
+                  }
+                }
+              }]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id-1",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "adid": "29681110",
+            "adomain": ["appnexus.com"],
+            "iurl": "http://nym1-ib.adnxs.com/cr?id=29681110",
+            "cid": "958",
+            "crid": "29681110",
+            "w": 300,
+            "h": 250,
+            "cat": ["IAB20-3"],
+            "ext": {
+              "appnexus": {
+                "brand_id": 1,
+                "brand_category_id": 1,
+                "auction_id": 8189378542222915032,
+                "bid_ad_type": 0,
+                "bidder_id": 2,
+                "ranking_price": 0.000000,
+                "deal_priority": 5
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/appnexus/appnexustest/supplemental/multiple-member-ids.json b/adapters/appnexus/appnexustest/supplemental/multiple-member-ids.json
new file mode 100644
index 00000000..ce56b01e
--- /dev/null
+++ b/adapters/appnexus/appnexustest/supplemental/multiple-member-ids.json
@@ -0,0 +1,131 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "ext": {
+      "prebid": {
+        "targeting": {"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"","withcategory":true}}
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "member": "103",
+            "inv_code": "abc",
+            "reserve": 20,
+            "position": "below",
+            "use_payment_rule": true
+          }
+        }
+      },
+      {
+        "id": "test-imp-id-2",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "member": "104",
+            "inv_code": "abc",
+            "reserve": 20,
+            "position": "below",
+            "use_pmt_rule": true,
+            "use_payment_rule": false
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://ib.adnxs.com/openrtb2?member_id=103",
+        "body": {
+          "id": "test-request-id",
+          "ext": {
+            "appnexus": {
+              "hb_source": 5,
+              "brand_category_uniqueness": true,
+              "include_brand_category": true
+            },
+            "prebid": {
+              "targeting": {"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"","withcategory":true}}
+            }
+          },
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [
+                  {"w":300,"h":250},
+                  {"w":300,"h":600}
+                ],
+                "w": 300,
+                "h": 250,
+                "pos": 3
+              },
+              "tagid": "abc",
+              "bidfloor": 20,
+              "ext": {
+                "appnexus": {
+                  "use_pmt_rule": true
+                }
+              }
+            },
+            {
+              "id":"test-imp-id-2",
+              "banner": {
+                "format": [
+                  {"w":300,"h":250},
+                  {"w":300,"h":600}
+                ],
+                "w": 300,
+                "h": 250,
+                "pos": 3
+              },
+              "tagid": "abc",
+              "bidfloor": 20,
+              "ext": {
+                "appnexus": {
+                  "use_pmt_rule": true
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "All request.imp[i].ext.prebid.bidder.appnexus.member params must match. Request contained: [103 104]",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/appnexus/appnexustest/supplemental/no-placementId-and-member-invcode.json b/adapters/appnexus/appnexustest/supplemental/no-placementId-and-member-invcode.json
new file mode 100644
index 00000000..c498b7e0
--- /dev/null
+++ b/adapters/appnexus/appnexustest/supplemental/no-placementId-and-member-invcode.json
@@ -0,0 +1,36 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id-1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": 0,
+            "trafficSourceCode": "xyz",
+            "position": "above"
+          }
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "No placement or member+invcode provided",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/appnexus/appnexustest/supplemental/status-400.json b/adapters/appnexus/appnexustest/supplemental/status-400.json
new file mode 100644
index 00000000..dc889012
--- /dev/null
+++ b/adapters/appnexus/appnexustest/supplemental/status-400.json
@@ -0,0 +1,80 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placement_id": 1,
+            "position": "above"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://ib.adnxs.com/openrtb2",
+        "body": {
+          "id": "test-request-id",
+          "ext": {
+            "appnexus": {
+              "hb_source": 5
+            },
+            "prebid": {}
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "pos": 1,
+                "h": 250
+              },
+              "ext": {
+                "appnexus": {
+                  "placement_id": 1
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json
index 5a453979..86fa14aa 100644
--- a/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json
+++ b/adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json
@@ -47,7 +47,7 @@
 			"id": "test-request-id",
 			"ext": {
 				"appnexus": {
-				 "adpod_id": "5577006791947779410",
+				 "adpod_id": "10",
 				  "hb_source": 6
 				},
 				"prebid": {}
diff --git a/adapters/pwbid/params_test.go b/adapters/pwbid/params_test.go
new file mode 100644
index 00000000..e16fd13c
--- /dev/null
+++ b/adapters/pwbid/params_test.go
@@ -0,0 +1,49 @@
+package pwbid
+
+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 schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderPWBid, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderPWBid, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"siteId":"39f43a","bidFloor":0.10,"isTest":false}`,
+	`{"siteId":"39f43a","bidFloor":0.10,"isTest":true}`,
+	`{"siteId":"39f43a","bidFloor":0.10}`,
+	`{"siteId":"39f43a"}`,
+}
+
+var invalidParams = []string{
+	`{"siteId":42,"bidFloor":"asdf","isTest":123}`,
+	`{"siteId":}`,
+	`{"bidFloor":}`,
+	`{"bidFloor":0.10}`,
+	`null`,
+}
diff --git a/adapters/pwbid/pwbid_test.go b/adapters/pwbid/pwbid_test.go
new file mode 100644
index 00000000..21a41942
--- /dev/null
+++ b/adapters/pwbid/pwbid_test.go
@@ -0,0 +1,21 @@
+package pwbid
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderPWBid, config.Adapter{
+		Endpoint: "https://bid.pubwise.io/prebid"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 842, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "pwbidtest", bidder)
+}
diff --git a/adapters/pwbid/pwbidtest/exemplary/banner.json b/adapters/pwbid/pwbidtest/exemplary/banner.json
new file mode 100644
index 00000000..ba618cb8
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/exemplary/banner.json
@@ -0,0 +1,93 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id-banner",
+    "imp": [
+      {
+        "id": "test-imp-id-banner",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "3943fa"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id-banner",
+          "imp": [
+            {
+              "id": "test-imp-id-banner",
+              "banner": {
+                "format": [
+                  {
+                    "w": 728,
+                    "h": 90
+                  }
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "3943fa"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id-banner",
+          "seatbid": [
+            {
+              "seat": "pwbid",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-imp-id-banner",
+                  "price": 0.500000,
+                  "adm": "some-test-ad-banner",
+                  "crid": "crid_10",
+                  "w": 728,
+                  "h": 90
+                }
+              ]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id-banner",
+            "price": 0.5,
+            "adm": "some-test-ad-banner",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/pwbid/pwbidtest/exemplary/native.json b/adapters/pwbid/pwbidtest/exemplary/native.json
new file mode 100644
index 00000000..907c16d4
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/exemplary/native.json
@@ -0,0 +1,81 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id-native",
+    "imp": [
+      {
+        "id": "test-imp-id-native",
+        "native": {
+          "request": ""
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "39f43a"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id-native",
+          "imp": [
+            {
+              "id": "test-imp-id-native",
+              "native": {
+                "request": ""
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "39f43a"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id-native",
+          "seatbid": [
+            {
+              "seat": "pwbid",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id-native",
+                "price": 0.500000,
+                "adm": "some-test-ad-native",
+                "crid": "crid_10",
+                "w": 728,
+                "h": 90
+              }]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id-native",
+            "price": 0.5,
+            "adm": "some-test-ad-native",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/pwbid/pwbidtest/exemplary/optional-params.json b/adapters/pwbid/pwbidtest/exemplary/optional-params.json
new file mode 100644
index 00000000..a080be90
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/exemplary/optional-params.json
@@ -0,0 +1,97 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id-banner",
+    "imp": [
+      {
+        "id": "test-imp-id-banner",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "3943fa",
+            "bidFloor": 0.10,
+            "isTest": false
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id-banner",
+          "imp": [
+            {
+              "id": "test-imp-id-banner",
+              "banner": {
+                "format": [
+                  {
+                    "w": 728,
+                    "h": 90
+                  }
+                ]
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "3943fa",
+                  "bidFloor": 0.10,
+                  "isTest": false
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id-banner",
+          "seatbid": [
+            {
+              "seat": "pwbid",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-imp-id-banner",
+                  "price": 0.500000,
+                  "adm": "some-test-ad-banner",
+                  "crid": "crid_10",
+                  "w": 728,
+                  "h": 90
+                }
+              ]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id-banner",
+            "price": 0.5,
+            "adm": "some-test-ad-banner",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/pwbid/pwbidtest/exemplary/video.json b/adapters/pwbid/pwbidtest/exemplary/video.json
new file mode 100644
index 00000000..b74c780d
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/exemplary/video.json
@@ -0,0 +1,91 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-video-id",
+        "video": {
+          "mimes": ["video/mp4"],
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "39f43a"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-video-id",
+              "video": {
+                "mimes": ["video/mp4"],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "39f43a"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id-test-video-id",
+          "seatbid": [
+            {
+              "seat": "test-seat",
+              "bid": [
+                {
+                  "id": "5dce6055-a93c-1fd0-8c29-14afc3e510fd",
+                  "impid": "test-video-id",
+                  "price": 0.1529,
+                  "nurl": "test-win",
+                  "adm": "test-video",
+                  "adid": "92-288",
+                  "adomain": ["advertiserdomain.com"],
+                  "crid": "288",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "5dce6055-a93c-1fd0-8c29-14afc3e510fd",
+            "impid": "test-video-id",
+            "price": 0.1529,
+            "nurl": "test-win",
+            "adm": "test-video",
+            "adid": "92-288",
+            "adomain": ["advertiserdomain.com"],
+            "crid": "288",
+            "w": 300,
+            "h": 250
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json b/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json
new file mode 100644
index 00000000..146ba93a
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json
@@ -0,0 +1,51 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "39f43a"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "39f43a"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unexpected end of JSON input",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-204.json b/adapters/pwbid/pwbidtest/supplemental/response-204.json
new file mode 100644
index 00000000..5fff7ee3
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/supplemental/response-204.json
@@ -0,0 +1,46 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "39f43a"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "39f43a"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-400.json b/adapters/pwbid/pwbidtest/supplemental/response-400.json
new file mode 100644
index 00000000..d594e571
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/supplemental/response-400.json
@@ -0,0 +1,51 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "39f43a"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "39f43a"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 400
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-500.json b/adapters/pwbid/pwbidtest/supplemental/response-500.json
new file mode 100644
index 00000000..fa3d4d06
--- /dev/null
+++ b/adapters/pwbid/pwbidtest/supplemental/response-500.json
@@ -0,0 +1,51 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "bidder": {
+            "siteId": "39f43a"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bid.pubwise.io/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "ext": {
+                "bidder": {
+                  "siteId": "39f43a"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 500
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/taboola/params_test.go b/adapters/taboola/params_test.go
index 25b6a75d..51a9833c 100644
--- a/adapters/taboola/params_test.go
+++ b/adapters/taboola/params_test.go
@@ -34,6 +34,7 @@ func TestInvalidParams(t *testing.T) {
 
 var validParams = []string{
 	`{"publisherId" : "1", "tagid": "tag-id-for-example"}`,
+	`{"publisherId" : "1", "tagId": "tag-id-for-example"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","position":1}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","pageType":"pageType"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
@@ -41,14 +42,14 @@ var validParams = []string{
 
 var invalidParams = []string{
 	`{}`,
-	`{"publisherId" : "1"}`,
+	`{"tagId" : "1"}`,
+	`{"publisherId" : "1", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
 	`{"publisherId" : 1, "tagid": "tag-id-for-example"}`,
 	`{"publisherId" : "1"", "tagid": 2}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "bcat":"incorrect-type"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "badv":"incorrect-type"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "bidfloor":"incorrect-type"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example", "publisherDomain":1}`,
-	`{"publisherId" : "1", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
 	`{"tagid": "tag-id-for-example", "bcat": ["excluded-category"], "badv": ["excluded-advertiser"], "bidfloor": 1.2, "publisherDomain": "http://domain.com"}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","position":null}`,
 	`{"publisherId" : "1", "tagid": "tag-id-for-example","position":"1"}`,
diff --git a/adapters/taboola/taboola_test.go b/adapters/taboola/taboola_test.go
index b5e87f08..320d08da 100644
--- a/adapters/taboola/taboola_test.go
+++ b/adapters/taboola/taboola_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderTaboola, config.Adapter{
-		Endpoint: "http://{{.MediaType}}.whatever.com/{{.Host}}/{{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+		Endpoint: "http://{{.MediaType}}.whatever.com/{{.GvlID}}/{{.PublisherID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 12, DataCenter: "2"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -32,5 +32,5 @@ func TestEmptyExternalUrl(t *testing.T) {
 
 	bidderTaboola := bidder.(*adapter)
 
-	assert.Equal(t, "", bidderTaboola.hostName)
+	assert.Equal(t, "", bidderTaboola.gvlID)
 }
diff --git a/adapters/taboola/taboolatest/exemplary/banner.json b/adapters/taboola/taboolatest/exemplary/banner.json
index a3f819b8..e550c66e 100644
--- a/adapters/taboola/taboolatest/exemplary/banner.json
+++ b/adapters/taboola/taboolatest/exemplary/banner.json
@@ -23,7 +23,8 @@
         "ext": {
           "bidder": {
             "publisherId": "publisher-id",
-            "tagid": "tag-id"
+            "tagid": "tag-id",
+            "tagId": "tag-Id"
           }
         }
       }
@@ -42,7 +43,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
@@ -64,11 +65,12 @@
                   }
                 ]
               },
-              "tagid" : "tag-id",
+              "tagid" : "tag-Id",
               "ext": {
                 "bidder": {
                   "publisherId": "publisher-id",
-                  "tagid": "tag-id"
+                  "tagid": "tag-id",
+                  "tagId": "tag-Id"
                 }
               }
             }
diff --git a/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json b/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json
new file mode 100644
index 00000000..830af06d
--- /dev/null
+++ b/adapters/taboola/taboolatest/exemplary/bannerResolveMacro.json
@@ -0,0 +1,154 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            },
+            {
+              "w": 160,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "publisher-id",
+            "tagid": "tag-id",
+            "tagId": "tag-Id"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "http://domain.com",
+      "page": "http://page-domain.com",
+      "ref": "http://page-domain.com"
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+      "h": 300,
+      "w": 300
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://display.whatever.com/12/publisher-id",
+        "body": {
+          "id": "request-id",
+          "imp": [
+            {
+              "id": "impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  },
+                  {
+                    "w": 160,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid" : "tag-Id",
+              "ext": {
+                "bidder": {
+                  "publisherId": "publisher-id",
+                  "tagid": "tag-id",
+                  "tagId": "tag-Id"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "publisher-id",
+            "name": "publisher-id",
+            "domain": "http://domain.com",
+            "page": "http://page-domain.com",
+            "ref": "http://page-domain.com",
+            "publisher": {
+              "id": "publisher-id"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+            "h": 300,
+            "w": 300
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "123",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "request-id",
+                  "impid": "impression-id",
+                  "price": 2.1,
+                  "adid": "1",
+                  "adm": "<hrml></html>&wp=${AUCTION_PRICE}",
+                  "adomain": [
+                    "adomain.com"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "w": 300,
+                  "h": 250,
+                  "exp": 60,
+                  "lurl": "https://lurl.domain.com/"
+                }
+              ],
+              "seat": "1111"
+            }
+          ],
+          "bidid": "123",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "request-id",
+            "impid": "impression-id",
+            "price": 2.1,
+            "adid": "1",
+            "adm": "<hrml></html>&wp=2.1",
+            "adomain": [
+              "adomain.com"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "w": 300,
+            "h": 250,
+            "exp": 60,
+            "lurl": "https://lurl.domain.com/"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json b/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json
index 7ba02f4e..9d603b8a 100644
--- a/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json
+++ b/adapters/taboola/taboolatest/exemplary/multiFormatImpressionsRequest.json
@@ -80,7 +80,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://native.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://native.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
@@ -150,7 +150,7 @@
     },
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/exemplary/native.json b/adapters/taboola/taboolatest/exemplary/native.json
index 0d2c0417..88863fdf 100644
--- a/adapters/taboola/taboolatest/exemplary/native.json
+++ b/adapters/taboola/taboolatest/exemplary/native.json
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://native.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://native.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json b/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json
new file mode 100644
index 00000000..03f05db5
--- /dev/null
+++ b/adapters/taboola/taboolatest/exemplary/nativeResolveMacro.json
@@ -0,0 +1,125 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "native": {
+          "request": "\"{\\\"ver\\\":\\\"1.1\\\",\\\"plcmttype\\\": 4,\\\"plcmtcnt\\\": 1,\\\"assets\\\":[{\\\"id\\\": 1,\\\"required\\\": 1,\\\"title\\\":{\\\"len\\\": 140}},{\\\"id\\\": 4,\\\"required\\\": 1,\\\"img\\\":{\\\"type\\\": 3,\\\"wmin\\\": 600,\\\"hmin\\\": 315}},{\\\"id\\\": 6,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 2,\\\"len\\\": 205}},{\\\"id\\\": 5,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 1,\\\"len\\\": 140}}]}}\"",
+          "ver": "1.1"
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "publisher-id",
+            "tagid": "tag-id"
+          }
+        }
+      }
+    ],
+    "site": {
+      "domain": "http://domain.com",
+      "page": "http://page-domain.com",
+      "ref": "http://page-domain.com"
+    },
+    "device": {
+      "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+      "h": 300,
+      "w": 300
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://native.whatever.com/12/publisher-id",
+        "body": {
+          "id": "request-id",
+          "imp": [
+            {
+              "id": "impression-id",
+              "native": {
+                "request": "\"{\\\"ver\\\":\\\"1.1\\\",\\\"plcmttype\\\": 4,\\\"plcmtcnt\\\": 1,\\\"assets\\\":[{\\\"id\\\": 1,\\\"required\\\": 1,\\\"title\\\":{\\\"len\\\": 140}},{\\\"id\\\": 4,\\\"required\\\": 1,\\\"img\\\":{\\\"type\\\": 3,\\\"wmin\\\": 600,\\\"hmin\\\": 315}},{\\\"id\\\": 6,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 2,\\\"len\\\": 205}},{\\\"id\\\": 5,\\\"required\\\": 1,\\\"data\\\":{\\\"type\\\": 1,\\\"len\\\": 140}}]}}\"",
+                "ver": "1.1"
+              },
+              "tagid": "tag-id",
+              "ext": {
+                "bidder": {
+                  "publisherId": "publisher-id",
+                  "tagid": "tag-id"
+                }
+              }
+            }
+          ],
+          "site": {
+            "id": "publisher-id",
+            "name": "publisher-id",
+            "domain": "http://domain.com",
+            "page": "http://page-domain.com",
+            "ref": "http://page-domain.com",
+            "publisher": {
+              "id": "publisher-id"
+            }
+          },
+          "device": {
+            "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
+            "h": 300,
+            "w": 300
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "123",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "request-id",
+                  "impid": "impression-id",
+                  "price": 2.1,
+                  "adid": "1",
+                  "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":7,\"required\":1,\"img\":{\"url\":\"http://image.taboola.com\",\"w\":600,\"h\":315}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Title For Example\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://clickUrl.com\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://tracker.com\"},{\"event\":1,\"method\":2,\"url\":\"http://tracker-2.com\"}]}&wp=${AUCTION_PRICE}",
+                  "adomain": [
+                    "adomain.comadomain.com"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "lurl": "https://lurl.domain.com/",
+                  "nurl": "nurl.com&wp=${AUCTION_PRICE}"
+                }
+              ],
+              "seat": "1111"
+            }
+          ],
+          "bidid": "123",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "request-id",
+            "impid": "impression-id",
+            "price": 2.1,
+            "adid": "1",
+            "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":7,\"required\":1,\"img\":{\"url\":\"http://image.taboola.com\",\"w\":600,\"h\":315}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Title For Example\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://clickUrl.com\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://tracker.com\"},{\"event\":1,\"method\":2,\"url\":\"http://tracker-2.com\"}]}&wp=2.1",
+
+          "adomain": [
+              "adomain.comadomain.com"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "lurl": "https://lurl.domain.com/",
+            "nurl": "nurl.com&wp=2.1"
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/taboola/taboolatest/exemplary/withPageType.json b/adapters/taboola/taboolatest/exemplary/withPageType.json
index c1e303d5..0f0d6435 100644
--- a/adapters/taboola/taboolatest/exemplary/withPageType.json
+++ b/adapters/taboola/taboolatest/exemplary/withPageType.json
@@ -43,7 +43,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/exemplary/withPosition.json b/adapters/taboola/taboolatest/exemplary/withPosition.json
index 4f62dfa4..ea1fdbdd 100644
--- a/adapters/taboola/taboolatest/exemplary/withPosition.json
+++ b/adapters/taboola/taboolatest/exemplary/withPosition.json
@@ -43,7 +43,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json
index 56d790db..c4a4279e 100644
--- a/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json
+++ b/adapters/taboola/taboolatest/supplemental/bidParamsOverrideRequestFields.json
@@ -45,7 +45,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/bidderServerError.json b/adapters/taboola/taboolatest/supplemental/bidderServerError.json
index 5c1670fb..949ceb93 100644
--- a/adapters/taboola/taboolatest/supplemental/bidderServerError.json
+++ b/adapters/taboola/taboolatest/supplemental/bidderServerError.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json
index 435f5d4d..b7d2f2be 100644
--- a/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json
+++ b/adapters/taboola/taboolatest/supplemental/emptyReponseFromBidder.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json b/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
index fafdd8a7..6e01c457 100644
--- a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
+++ b/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
@@ -40,7 +40,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json
index 18500f3e..7373d05c 100644
--- a/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json
+++ b/adapters/taboola/taboolatest/supplemental/incorrectResponseImpMapping.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json
index 720398f9..24998b4b 100644
--- a/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json
+++ b/adapters/taboola/taboolatest/supplemental/multiImpressionsRequest.json
@@ -51,7 +51,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json
index 580c7d28..cdf39eee 100644
--- a/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json
+++ b/adapters/taboola/taboolatest/supplemental/optionalParamsProvided.json
@@ -46,7 +46,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json
index 706a0ac2..d8069578 100644
--- a/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json
+++ b/adapters/taboola/taboolatest/supplemental/unexpectedStatusCode.json
@@ -42,7 +42,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://display.whatever.com/hosturl.com/publisher-id",
+        "uri": "http://display.whatever.com/12/publisher-id",
         "body": {
           "id": "request-id",
           "imp": [
diff --git a/adservertargeting/adservertargeting_test.go b/adservertargeting/adservertargeting_test.go
new file mode 100644
index 00000000..f9daab7c
--- /dev/null
+++ b/adservertargeting/adservertargeting_test.go
@@ -0,0 +1,365 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/openrtb/v17/openrtb3"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+	"net/url"
+	"testing"
+)
+
+func TestExtractAdServerTargeting(t *testing.T) {
+
+	r := openrtb2.BidRequest{
+		ID: "anyID",
+		Imp: []openrtb2.Imp{
+			{
+				ID: "imp1", BidFloor: 10.00,
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1, "test": {"testUser": "user1"}}, "other": "otherImp", "bidder1": {"tagid": 111, "placementId": "test1"}}`),
+			},
+			{
+				ID: "imp2", BidFloor: 20.00,
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1, "test": {"testUser": "user2"}}, "other": "otherImp", "bidder1": {"tagid": 222, "placementId": "test2"}}`),
+			},
+			{
+				ID: "imp3", BidFloor: 30.00,
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1, "test": {"testUser": "user3"}}, "other": "otherImp", "bidder1": {"tagid": 333, "placementId": "test3"}}`),
+			},
+		},
+		User: &openrtb2.User{
+			ID:       "testUser",
+			Yob:      2000,
+			Keywords: "keywords",
+		},
+		Ext: json.RawMessage(reqExt),
+	}
+
+	rw := &openrtb_ext.RequestWrapper{BidRequest: &r}
+
+	p := "https://www.test-url.com?ampkey=testAmpKey&data-override-height=400"
+	u, _ := url.Parse(p)
+	params := u.Query()
+	reqBytes, err := json.Marshal(r)
+	assert.NoError(t, err, "unexpected req marshal error")
+
+	res, warnings := collect(rw, reqBytes, params)
+	assert.Len(t, warnings, 2, "incorrect warnings")
+	assert.Equal(t, "incorrect value type for path: imp.ext.prebid.test, value can only be string or number", warnings[0].Message, "incorrect warning")
+	assert.Equal(t, "incorrect value type for path: ext.prebid.targeting.includebrandcategory, value can only be string or number", warnings[1].Message, "incorrect warning")
+
+	assert.Len(t, res.RequestTargetingData, 5, "incorrect request targeting data length")
+	assert.Len(t, res.ResponseTargetingData, 4, "incorrect response targeting data length")
+
+	assert.Equal(t, res.RequestTargetingData["hb_amp_param"].SingleVal, json.RawMessage(`testAmpKey`), "incorrect requestTargetingData value for key: hb_amp_param")
+
+	assert.Len(t, res.RequestTargetingData["hb_req_imp_ext_bidder_param"].TargetingValueByImpId, 3, "incorrect requestTargetingData length for key: hb_req_imp_ext_bidder_param")
+	assert.Equal(t, res.RequestTargetingData["hb_req_imp_ext_bidder_param"].TargetingValueByImpId["imp1"], []byte(`111`), "incorrect requestTargetingData value for key: hb_req_imp_ext_bidder_param.imp1")
+	assert.Equal(t, res.RequestTargetingData["hb_req_imp_ext_bidder_param"].TargetingValueByImpId["imp2"], []byte(`222`), "incorrect requestTargetingData value for key: hb_req_imp_ext_bidder_param.imp2")
+	assert.Equal(t, res.RequestTargetingData["hb_req_imp_ext_bidder_param"].TargetingValueByImpId["imp3"], []byte(`333`), "incorrect requestTargetingData value for key: hb_req_imp_ext_bidder_param.imp3")
+
+	assert.Len(t, res.RequestTargetingData["hb_req_imp_param"].TargetingValueByImpId, 3, "incorrect requestTargetingData length for key: hb_req_imp_param")
+	assert.Equal(t, res.RequestTargetingData["hb_req_imp_param"].TargetingValueByImpId["imp1"], []byte(`10`), "incorrect requestTargetingData value for key: hb_req_imp_param.imp1")
+	assert.Equal(t, res.RequestTargetingData["hb_req_imp_param"].TargetingValueByImpId["imp2"], []byte(`20`), "incorrect requestTargetingData value for key: hb_req_imp_param.imp2")
+	assert.Equal(t, res.RequestTargetingData["hb_req_imp_param"].TargetingValueByImpId["imp3"], []byte(`30`), "incorrect requestTargetingData value for key: hb_req_imp_param.imp3")
+
+	assert.Equal(t, res.RequestTargetingData["hb_req_user_param"].SingleVal, json.RawMessage(`2000`), "incorrect requestTargetingData value for key: hb_req_user_param")
+	assert.Equal(t, res.RequestTargetingData["hb_static_thing"].SingleVal, json.RawMessage(`test-static-value`), "incorrect requestTargetingData value for key: hb_static_thing")
+
+	assert.Equal(t, res.ResponseTargetingData[0].Key, "{{BIDDER}}_custom1", "incorrect ResponseTargetingData.Key")
+	assert.True(t, res.ResponseTargetingData[0].HasMacro, "incorrect ResponseTargetingData.HasMacro")
+	assert.Equal(t, res.ResponseTargetingData[0].Path, "seatbid.bid.ext.custom1", "incorrect ResponseTargetingData.Path")
+
+	assert.Equal(t, res.ResponseTargetingData[1].Key, "custom2", "incorrect ResponseTargetingData.Key")
+	assert.False(t, res.ResponseTargetingData[1].HasMacro, "incorrect ResponseTargetingData.HasMacro")
+	assert.Equal(t, res.ResponseTargetingData[1].Path, "seatbid.bid.ext.custom2", "incorrect ResponseTargetingData.Path")
+
+}
+
+func TestResolveAdServerTargeting(t *testing.T) {
+	nbr := openrtb3.NoBidReason(2)
+	resp := &openrtb2.BidResponse{
+		ID:         "testResponse",
+		Cur:        "USD",
+		BidID:      "testBidId",
+		CustomData: "testCustomData",
+		NBR:        &nbr,
+		SeatBid: []openrtb2.SeatBid{
+			{
+				Seat: "appnexus",
+				Bid: []openrtb2.Bid{
+					{ID: "bidA1", ImpID: "imp1", Price: 10, Cat: []string{"cat11", "cat12"}, Ext: []byte(`{"prebid": {"foo": "bar1"}}`)},
+					{ID: "bidA2", ImpID: "imp2", Price: 20, Cat: []string{"cat21", "cat22"}, Ext: []byte(`{"prebid": {"foo": "bar2"}}`)},
+					{ID: "bidA3", ImpID: "imp3", Price: 30, Cat: []string{"cat31", "cat32"}, Ext: []byte(`{"prebid": {"foo": "bar3"}}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barApn"}}`),
+			},
+			{
+				Seat: "rubicon",
+				Bid: []openrtb2.Bid{
+					{ID: "bidR1", ImpID: "imp1", Price: 11, Cat: []string{"cat111", "cat112"}, Ext: []byte(`{"prebid": {"foo": "bar11", "targeting":{"hb_amp_param":"testInputKey1", "testInput": 111}}}`)},
+					{ID: "bidR2", ImpID: "imp2", Price: 22, Cat: []string{"cat221", "cat222"}, Ext: []byte(`{"prebid": {"foo": "bar22", "targeting":{"hb_amp_param":"testInputKey2", "testInput": 222}}}`)},
+					{ID: "bidR3", ImpID: "imp3", Price: 33, Cat: []string{"cat331", "cat332"}, Ext: []byte(`{"prebid": {"foo": "bar33", "targeting":{"hb_amp_param":"testInputKey3", "testInput": 333}}}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barRubicon"}}`),
+			},
+		},
+		Ext: []byte(`{"prebid": {"seatExt": "true"}}`),
+	}
+
+	adServerTargeting := &adServerTargetingData{
+		RequestTargetingData: map[string]RequestTargetingData{
+			"hb_amp_param": {SingleVal: json.RawMessage(`testAmpKey`), TargetingValueByImpId: nil},
+			"hb_imp_param": {SingleVal: nil, TargetingValueByImpId: map[string][]byte{
+				"imp1": []byte(`111`),
+				"imp2": []byte(`222`),
+				"imp3": []byte(`333`),
+			},
+			},
+		},
+		ResponseTargetingData: []ResponseTargetingData{
+			{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "seatbid.bid.cat"},
+			{Key: "custom2", HasMacro: false, Path: "seatbid.bid.ext.prebid.foo"},
+			{Key: "custom3", HasMacro: false, Path: "seatbid.bid.price"},
+			{Key: "custom4", HasMacro: false, Path: "seatbid.ext.testData.foo"},
+			{Key: "custom5", HasMacro: false, Path: "cur"},
+			{Key: "custom6", HasMacro: false, Path: "id"},
+			{Key: "custom7", HasMacro: false, Path: "bidid"},
+			{Key: "custom8", HasMacro: false, Path: "customdata"},
+			{Key: "custom9", HasMacro: false, Path: "nbr"},
+			{Key: "{{BIDDER}}_custom6", HasMacro: true, Path: "ext.prebid.seatExt"},
+		},
+	}
+	bidResponse, warnings := resolve(adServerTargeting, resp, nil, nil)
+
+	assert.Len(t, warnings, 6, "incorrect warnings number")
+	assert.NotNil(t, bidResponse, "incorrect resolved targeting data")
+	assert.Len(t, bidResponse.SeatBid, 2, "incorrect seat bids number")
+
+	assert.Len(t, bidResponse.SeatBid[0].Bid, 3, "incorrect bids number for bidder : %", "appnexus")
+	assert.Len(t, bidResponse.SeatBid[1].Bid, 3, "incorrect bids number for bidder : %", "rubicon")
+
+	assert.JSONEq(t, seatBid0Bid0Ext, string(bidResponse.SeatBid[0].Bid[0].Ext), "incorrect ext for SeatBid[0].Bid[0]")
+	assert.JSONEq(t, seatBid0Bid1Ext, string(bidResponse.SeatBid[0].Bid[1].Ext), "incorrect ext for SeatBid[0].Bid[1]")
+	assert.JSONEq(t, seatBid0Bid2Ext, string(bidResponse.SeatBid[0].Bid[2].Ext), "incorrect ext for SeatBid[0].Bid[2]")
+	assert.JSONEq(t, seatBid1Bid0Ext, string(bidResponse.SeatBid[1].Bid[0].Ext), "incorrect ext for SeatBid[1].Bid[0]")
+	assert.JSONEq(t, seatBid1Bid1Ext, string(bidResponse.SeatBid[1].Bid[1].Ext), "incorrect ext for SeatBid[1].Bid[1]")
+	assert.JSONEq(t, seatBid1Bid2Ext, string(bidResponse.SeatBid[1].Bid[2].Ext), "incorrect ext for SeatBid[1].Bid[2]")
+
+}
+
+func TestResolveAdServerTargetingForMultiBidAndOneImp(t *testing.T) {
+	resp := &openrtb2.BidResponse{
+		ID:  "testResponse",
+		Cur: "USD",
+		SeatBid: []openrtb2.SeatBid{
+			{
+				Seat: "appnexus",
+				Bid: []openrtb2.Bid{
+					{ID: "bidA1", ImpID: "imp1", Price: 10, Cat: []string{"cat11", "cat12"}, Ext: []byte(`{"prebid": {"foo": "bar1"}}`)},
+					{ID: "bidA2", ImpID: "imp1", Price: 20, Cat: []string{"cat21", "cat22"}, Ext: []byte(`{"prebid": {"foo": "bar2"}}`)},
+					{ID: "bidA3", ImpID: "imp1", Price: 30, Cat: []string{"cat31", "cat32"}, Ext: []byte(`{"prebid": {"foo": "bar3"}}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barApn"}}`),
+			},
+		},
+		Ext: []byte(`{"prebid": {"seatExt": "true"}}`),
+	}
+
+	adServerTargeting := &adServerTargetingData{
+		RequestTargetingData: nil,
+		ResponseTargetingData: []ResponseTargetingData{
+			{Key: "custom_attribute", HasMacro: false, Path: "seatbid.bid.ext.prebid.foo"},
+		},
+	}
+	truncateTargetingAttr := 11
+	bidResponse, warnings := resolve(adServerTargeting, resp, nil, &truncateTargetingAttr)
+
+	assert.Empty(t, warnings, "unexpected error")
+	assert.Equal(t, bidResponse.ID, "testResponse", "incorrect")
+	assert.Len(t, bidResponse.SeatBid, 1, "incorrect seat bids number")
+	assert.Len(t, bidResponse.SeatBid[0].Bid, 3, "incorrect bids number")
+
+	assert.JSONEq(t, bid0Ext, string(bidResponse.SeatBid[0].Bid[0].Ext), "incorrect ext for SeatBid[0].Bid[0]")
+	assert.JSONEq(t, bid1Ext, string(bidResponse.SeatBid[0].Bid[1].Ext), "incorrect ext for SeatBid[0].Bid[1]")
+	assert.JSONEq(t, bid2Ext, string(bidResponse.SeatBid[0].Bid[2].Ext), "incorrect ext for SeatBid[0].Bid[2]")
+
+}
+
+func TestProcessAdServerTargetingFull(t *testing.T) {
+
+	r := openrtb2.BidRequest{
+		ID: "anyID",
+		Imp: []openrtb2.Imp{
+			{
+				ID: "imp1", BidFloor: 10.00,
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1, "test": {"testUser": "user1"}}, "other": "otherImp", "bidder1": {"tagid": 111, "placementId": "test1"}}`),
+			},
+			{
+				ID: "imp2", BidFloor: 20.00,
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1, "test": {"testUser": "user2"}}, "other": "otherImp", "bidder1": {"tagid": 222, "placementId": "test2"}}`),
+			},
+			{
+				ID: "imp3", BidFloor: 30.00,
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1, "test": {"testUser": "user3"}}, "other": "otherImp", "bidder1": {"tagid": 333, "placementId": "test3"}}`),
+			},
+		},
+		User: &openrtb2.User{
+			ID:       "testUser",
+			Yob:      2000,
+			Keywords: "keywords",
+		},
+		Ext: json.RawMessage(reqExt),
+	}
+
+	rw := &openrtb_ext.RequestWrapper{BidRequest: &r}
+
+	p := "https://www.test-url.com?ampkey=testAmpKey&data-override-height=400"
+	u, _ := url.Parse(p)
+	params := u.Query()
+
+	resp := &openrtb2.BidResponse{
+		ID:  "testResponse",
+		Cur: "USD",
+		SeatBid: []openrtb2.SeatBid{
+			{
+				Seat: "appnexus",
+				Bid: []openrtb2.Bid{
+					{ID: "bidA1", ImpID: "imp1", Price: 10, Cat: []string{"cat11", "cat12"}, Ext: []byte(`{"prebid": {"foo": "bar1"}, "custom1": 1111, "custom2": "a1111"}`)},
+					{ID: "bidA2", ImpID: "imp2", Price: 20, Cat: []string{"cat21", "cat22"}, Ext: []byte(`{"prebid": {"foo": "bar2"}, "custom1": 2222, "custom2": "a2222"}`)},
+					{ID: "bidA3", ImpID: "imp3", Price: 30, Cat: []string{"cat31", "cat32"}, Ext: []byte(`{"prebid": {"foo": "bar3"}, "custom1": 3333, "custom2": "a3333"}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barApn"}}`),
+			},
+			{
+				Seat: "rubicon",
+				Bid: []openrtb2.Bid{
+					{ID: "bidR1", ImpID: "imp1", Price: 11, Cat: []string{"cat111", "cat112"}, Ext: []byte(`{"custom1": 4444, "custom2": "r4444", "prebid": {"foo": "bar11", "targeting":{"hb_amp_param":"testInputKey1", "testInput": 111}}}`)},
+					{ID: "bidR2", ImpID: "imp2", Price: 22, Cat: []string{"cat221", "cat222"}, Ext: []byte(`{"custom1": 5555, "custom2": "r5555", "prebid": {"foo": "bar22", "targeting":{"hb_amp_param":"testInputKey2", "testInput": 222}}}`)},
+					{ID: "bidR3", ImpID: "imp3", Price: 33, Cat: []string{"cat331", "cat332"}, Ext: []byte(`{"custom1": 6666, "custom2": "r6666", "prebid": {"foo": "bar33", "targeting":{"hb_amp_param":"testInputKey3", "testInput": 333}}}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barRubicon"}}`),
+			},
+		},
+		Ext: []byte(`{"prebid": {"seatExt": "true"}}`),
+	}
+
+	bidResponseExt := &openrtb_ext.ExtBidResponse{Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)}
+
+	reqBytes, err := json.Marshal(r)
+	assert.NoError(t, err, "unexpected req marshal error")
+	targetingKeyLen := 0
+	resResp := Apply(rw, reqBytes, resp, params, bidResponseExt, &targetingKeyLen)
+	assert.Len(t, resResp.SeatBid, 2, "incorrect response: seat bid number")
+	assert.Len(t, bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], 2, "incorrect warnings number")
+	assert.Equal(t, "incorrect value type for path: imp.ext.prebid.test, value can only be string or number", bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral][0].Message, "incorrect warning")
+	assert.Equal(t, "incorrect value type for path: ext.prebid.targeting.includebrandcategory, value can only be string or number", bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral][1].Message, "incorrect warning")
+
+	apnBids := resResp.SeatBid[0].Bid
+	rbcBids := resResp.SeatBid[1].Bid
+
+	assert.Len(t, apnBids, 3, "Incorrect response: appnexus bids number")
+	assert.Len(t, rbcBids, 3, "Incorrect response: rubicon bid number")
+
+	assert.JSONEq(t, apnBid0Ext, string(apnBids[0].Ext), "incorrect ext for appnexus bid[0]")
+	assert.JSONEq(t, apnBid1Ext, string(apnBids[1].Ext), "incorrect ext for appnexus bid[1]")
+	assert.JSONEq(t, apnBid2Ext, string(apnBids[2].Ext), "incorrect ext for appnexus bid[2]")
+
+	assert.JSONEq(t, rbcBid0Ext, string(rbcBids[0].Ext), "incorrect ext for rubicon bid[0]")
+	assert.JSONEq(t, rbcBid1Ext, string(rbcBids[1].Ext), "incorrect ext for rubicon bid[1]")
+	assert.JSONEq(t, rbcBid2Ext, string(rbcBids[2].Ext), "incorrect ext for rubicon bid[2]")
+
+}
+
+func TestProcessAdServerTargetingWarnings(t *testing.T) {
+
+	r := openrtb2.BidRequest{
+		ID: "anyID",
+		Imp: []openrtb2.Imp{
+			{
+				ID:  "imp1",
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1 }, "other": "otherImp", "bidder1": {"placementId": "test1"}}`),
+			},
+			{
+				ID:  "imp2",
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1}, "other": "otherImp", "bidder1": {"placementId": "test2"}}`),
+			},
+			{
+				ID:  "imp3",
+				Ext: json.RawMessage(`{"prebid": {"is_rewarded_inventory": 1}, "other": "otherImp", "bidder1": {"placementId": "test3"}}`),
+			},
+		},
+		User: &openrtb2.User{
+			ID:       "testUser",
+			Keywords: "keywords",
+		},
+		Ext: json.RawMessage(reqExt),
+	}
+
+	rw := &openrtb_ext.RequestWrapper{BidRequest: &r}
+
+	p := "https://www.test-url.com?data-override-height=400"
+	u, _ := url.Parse(p)
+	params := u.Query()
+
+	resp := &openrtb2.BidResponse{
+		ID: "testResponse",
+		SeatBid: []openrtb2.SeatBid{
+			{
+				Seat: "appnexus",
+				Bid: []openrtb2.Bid{
+					{ID: "bidA1", ImpID: "imp1", Price: 10, Cat: []string{"cat11", "cat12"}, Ext: []byte(`{"prebid": {"foo": "bar1"}}`)},
+					{ID: "bidA2", ImpID: "imp2", Price: 20, Cat: []string{"cat21", "cat22"}, Ext: []byte(`{"prebid": {"foo": "bar2"}}`)},
+					{ID: "bidA3", ImpID: "imp3", Price: 30, Cat: []string{"cat31", "cat32"}, Ext: []byte(`{"prebid": {"foo": "bar3"}}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barApn"}}`),
+			},
+			{
+				Seat: "rubicon",
+				Bid: []openrtb2.Bid{
+					{ID: "bidR1", ImpID: "imp1", Price: 11, Cat: []string{"cat111", "cat112"}, Ext: []byte(`{"prebid": {"foo": "bar11", "targeting":{"hb_amp_param":"testInputKey1", "testInput": 111}}}`)},
+					{ID: "bidR2", ImpID: "imp2", Price: 22, Cat: []string{"cat221", "cat222"}, Ext: []byte(`{"prebid": {"foo": "bar22", "targeting":{"hb_amp_param":"testInputKey2", "testInput": 222}}}`)},
+					{ID: "bidR3", ImpID: "imp3", Price: 33, Cat: []string{"cat331", "cat332"}, Ext: []byte(`{"prebid": {"foo": "bar33", "targeting":{"hb_amp_param":"testInputKey3", "testInput": 333}}}`)},
+				},
+				Ext: []byte(`{"testData": {"foo": "barRubicon"}}`),
+			},
+		},
+		Ext: []byte(`{"prebid": {"seatExt": "true"}}`),
+	}
+
+	bidResponseExt := &openrtb_ext.ExtBidResponse{Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)}
+
+	reqBytes, err := json.Marshal(r)
+	assert.NoError(t, err, "unexpected req marshal error")
+	resResp := Apply(rw, reqBytes, resp, params, bidResponseExt, nil)
+	assert.Len(t, resResp.SeatBid, 2, "Incorrect response: seat bid number")
+
+	apnBids := resResp.SeatBid[0].Bid
+	rbcBids := resResp.SeatBid[1].Bid
+
+	assert.Len(t, apnBids, 3, "Incorrect response: appnexus bids number")
+	assert.Len(t, rbcBids, 3, "Incorrect response: rubicon bid number")
+
+	warnings := bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral]
+	assert.Len(t, warnings, 18, "Incorrect response: seat bid number")
+	assert.Equal(t, "value not found for path: ext.prebid.amp.data.ampkey", warnings[0].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: imp.ext.prebid.test", warnings[1].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: imp.ext.bidder1.tagid", warnings[2].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: imp.bidfloor", warnings[3].Message, "Incorrect warning")
+	assert.Equal(t, "incorrect value type for path: ext.prebid.targeting.includebrandcategory, value can only be string or number", warnings[4].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: user.yob", warnings[5].Message, "Incorrect warning")
+
+	assert.Equal(t, "value not found for path: ext.custom1 for bidder: appnexus, bid id: bidA1", warnings[6].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom2 for bidder: appnexus, bid id: bidA1", warnings[7].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom1 for bidder: appnexus, bid id: bidA2", warnings[8].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom2 for bidder: appnexus, bid id: bidA2", warnings[9].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom1 for bidder: appnexus, bid id: bidA3", warnings[10].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom2 for bidder: appnexus, bid id: bidA3", warnings[11].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom1 for bidder: rubicon, bid id: bidR1", warnings[12].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom2 for bidder: rubicon, bid id: bidR1", warnings[13].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom1 for bidder: rubicon, bid id: bidR2", warnings[14].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom2 for bidder: rubicon, bid id: bidR2", warnings[15].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom1 for bidder: rubicon, bid id: bidR3", warnings[16].Message, "Incorrect warning")
+	assert.Equal(t, "value not found for path: ext.custom2 for bidder: rubicon, bid id: bidR3", warnings[17].Message, "Incorrect warning")
+}
diff --git a/adservertargeting/requestcache_test.go b/adservertargeting/requestcache_test.go
new file mode 100644
index 00000000..5764f99e
--- /dev/null
+++ b/adservertargeting/requestcache_test.go
@@ -0,0 +1,109 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestRequestImpCache(t *testing.T) {
+	testCases := []struct {
+		description     string
+		inputRequest    json.RawMessage
+		expectedReqSize int
+		expectedImpsNum int
+		expectedError   bool
+	}{
+		{
+			description:     "valid request with 2 imps",
+			inputRequest:    json.RawMessage(reqValid),
+			expectedReqSize: 355,
+			expectedImpsNum: 2,
+			expectedError:   false,
+		},
+		{
+			description:     "invalid request ",
+			inputRequest:    json.RawMessage(reqInvalid),
+			expectedReqSize: 88,
+			expectedImpsNum: 0,
+			expectedError:   true,
+		},
+		{
+			description:     "valid request with no imps",
+			inputRequest:    json.RawMessage(reqNoImps),
+			expectedReqSize: 52,
+			expectedImpsNum: 0,
+			expectedError:   true,
+		},
+	}
+
+	for _, test := range testCases {
+		reqImpCache := requestCache{resolvedReq: test.inputRequest}
+
+		actualReq := reqImpCache.GetReqJson()
+		assert.Len(t, actualReq, test.expectedReqSize, "incorrect request returned")
+		actualImps, err := reqImpCache.GetImpsData()
+		assert.Len(t, actualImps, test.expectedImpsNum, "incorrect number of impressions returned")
+
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestBidsCache(t *testing.T) {
+
+	testCases := []struct {
+		description      string
+		inputBidder      string
+		inputBidId       string
+		inputBid         openrtb2.Bid
+		expectedBidBytes []byte
+		expectedError    bool
+	}{
+		{
+			description:      "valid bid not in cache for existing bidder",
+			inputBidder:      "bidderA",
+			inputBidId:       "bid3",
+			inputBid:         openrtb2.Bid{ID: "test_bid3"},
+			expectedBidBytes: []byte(`{"id":"test_bid3","impid":"","price":0}`),
+			expectedError:    false,
+		},
+		{
+			description:      "valid bid and not existing bidder",
+			inputBidder:      "bidderB",
+			inputBidId:       "bid1",
+			inputBid:         openrtb2.Bid{ID: "test_bid1"},
+			expectedBidBytes: []byte(`{"id":"test_bid1","impid":"","price":0}`),
+			expectedError:    false,
+		},
+		{
+			description:      "valid bid in cache",
+			inputBidder:      "bidderA",
+			inputBidId:       "bid2",
+			inputBid:         openrtb2.Bid{},
+			expectedBidBytes: []byte(`{"bidid":"test_bid2"}`),
+			expectedError:    false,
+		},
+	}
+
+	bCache := bidsCache{bids: map[string]map[string][]byte{
+		"bidderA": {
+			"bid1": []byte(`{"bidid":"test_bid1"}`),
+			"bid2": []byte(`{"bidid":"test_bid2"}`),
+		},
+	}}
+
+	for _, test := range testCases {
+		bidBytes, err := bCache.GetBid(test.inputBidder, test.inputBidId, test.inputBid)
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+			assert.Equal(t, test.expectedBidBytes, bidBytes, "incorrect bid returned")
+		}
+	}
+}
diff --git a/adservertargeting/requestlookup_test.go b/adservertargeting/requestlookup_test.go
new file mode 100644
index 00000000..25c60a29
--- /dev/null
+++ b/adservertargeting/requestlookup_test.go
@@ -0,0 +1,372 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+	"net/url"
+	"testing"
+)
+
+func TestGetAdServerTargeting(t *testing.T) {
+
+	testCases := []struct {
+		description       string
+		inputRequestExt   json.RawMessage
+		expectedTargeting []openrtb_ext.AdServerTarget
+		expectedError     bool
+	}{
+		{
+			description:       "valid request with no ext.prebid",
+			inputRequestExt:   json.RawMessage(``),
+			expectedTargeting: nil,
+			expectedError:     false,
+		},
+		{
+			description:       "valid request with correct ext.prebid, no ad server targeting",
+			inputRequestExt:   json.RawMessage(`{"prebid":{}}`),
+			expectedTargeting: nil,
+			expectedError:     false,
+		},
+		{
+			description:       "valid request with correct ext.prebid, and empty ad server targeting",
+			inputRequestExt:   json.RawMessage(`{"prebid":{"adservertargeting":[]}}`),
+			expectedTargeting: []openrtb_ext.AdServerTarget{},
+			expectedError:     false,
+		},
+		{
+			description: "valid request with correct ext.prebid, and with ad server targeting",
+			inputRequestExt: json.RawMessage(`{"prebid":{"adservertargeting":[
+					{"key": "adt_key",
+                    "source": "bidrequest",
+                    "value": "ext.prebid.data"}
+				]}}`),
+			expectedTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key", Source: "bidrequest", Value: "ext.prebid.data"},
+			},
+			expectedError: false,
+		},
+	}
+
+	for _, test := range testCases {
+		request := &openrtb_ext.RequestWrapper{
+			BidRequest: &openrtb2.BidRequest{ID: "req_id", Ext: test.inputRequestExt},
+		}
+
+		actualTargeting, err := getAdServerTargeting(request)
+
+		assert.Equal(t, test.expectedTargeting, actualTargeting, "targeting data doesn't match")
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestValidateAdServerTargeting(t *testing.T) {
+	testCases := []struct {
+		description       string
+		inputTargeting    []openrtb_ext.AdServerTarget
+		expectedTargeting []openrtb_ext.AdServerTarget
+		expectedWarnings  []openrtb_ext.ExtBidderMessage
+	}{
+		{
+			description: "valid targeting object",
+			inputTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key", Source: "bidrequest", Value: "ext.prebid.data"},
+			},
+			expectedTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key", Source: "bidrequest", Value: "ext.prebid.data"},
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage(nil),
+		},
+		{
+			description: "invalid targeting object: key",
+			inputTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "", Source: "bidrequest", Value: "ext.prebid.data"},
+			},
+			expectedTargeting: []openrtb_ext.AdServerTarget(nil),
+			expectedWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "Key is empty for the ad server targeting object at index 0"},
+			},
+		},
+		{
+			description: "invalid targeting object: source",
+			inputTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key", Source: "incorrect", Value: "ext.prebid.data"},
+			},
+			expectedTargeting: []openrtb_ext.AdServerTarget(nil),
+			expectedWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "Incorrect source for the ad server targeting object at index 0"},
+			},
+		},
+		{
+			description: "invalid targeting object: value",
+			inputTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key", Source: "static", Value: ""},
+			},
+			expectedTargeting: []openrtb_ext.AdServerTarget(nil),
+			expectedWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "Value is empty for the ad server targeting object at index 0"},
+			},
+		},
+		{
+			description: "valid and invalid targeting object",
+			inputTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key1", Source: "static", Value: "valid"},
+				{Key: "adt_key2", Source: "static", Value: ""},
+			},
+			expectedTargeting: []openrtb_ext.AdServerTarget{
+				{Key: "adt_key1", Source: "static", Value: "valid"},
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "Value is empty for the ad server targeting object at index 1"},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		actualTargeting, actualWarnings := validateAdServerTargeting(test.inputTargeting)
+		assert.Equal(t, test.expectedTargeting, actualTargeting, "incorrect targeting data")
+		assert.Equal(t, test.expectedWarnings, actualWarnings, "incorrect warnings")
+	}
+}
+
+func TestGetValueFromBidRequest(t *testing.T) {
+
+	testCases := []struct {
+		description       string
+		inputPath         string
+		expectedTargeting RequestTargetingData
+		expectedError     bool
+	}{
+		{
+			description:       "get existing value from query params",
+			inputPath:         "ext.prebid.amp.data.amp-key",
+			expectedTargeting: RequestTargetingData{SingleVal: json.RawMessage(`testAmpKey`)},
+			expectedError:     false,
+		},
+		{
+			description:       "get non-existing value from query params",
+			inputPath:         "ext.prebid.amp.data.amp-key-not-existing",
+			expectedTargeting: RequestTargetingData{},
+			expectedError:     true,
+		},
+		{
+			description: "get existing value from impressions",
+			inputPath:   "imp.banner.h",
+			expectedTargeting: RequestTargetingData{
+				TargetingValueByImpId: map[string][]byte{
+					"test_imp1": []byte(`350`),
+					"test_imp2": []byte(`360`),
+				},
+			},
+			expectedError: false,
+		},
+		{
+			description: "get existing value from impressions ext",
+			inputPath:   "imp.ext.appnexus.placementId",
+			expectedTargeting: RequestTargetingData{
+				TargetingValueByImpId: map[string][]byte{
+					"test_imp1": []byte(`123`),
+					"test_imp2": []byte(`456`),
+				},
+			},
+			expectedError: false,
+		},
+		{
+			description:       "get non-existing value from impressions",
+			inputPath:         "imp.video",
+			expectedTargeting: RequestTargetingData{},
+			expectedError:     true,
+		},
+		{
+			description: "get existing value from req",
+			inputPath:   "site.page",
+			expectedTargeting: RequestTargetingData{
+				SingleVal: json.RawMessage(`test.com`),
+			},
+			expectedError: false,
+		},
+		{
+			description:       "get non-existing value from req",
+			inputPath:         "app",
+			expectedTargeting: RequestTargetingData{},
+			expectedError:     true,
+		},
+	}
+
+	reqCache := requestCache{
+		resolvedReq: json.RawMessage(reqFullValid),
+	}
+
+	u, _ := url.Parse(testUrl)
+	params := u.Query()
+
+	for _, test := range testCases {
+		resTargetingData, err := getValueFromBidRequest(&reqCache, test.inputPath, params)
+
+		assert.Equal(t, test.expectedTargeting, resTargetingData, "incorrect targeting data returned")
+
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestGetValueFromQueryParam(t *testing.T) {
+	u, _ := url.Parse(testUrl)
+	params := u.Query()
+
+	testCases := []struct {
+		description   string
+		inputPath     string
+		expectedValue json.RawMessage
+		expectedError bool
+	}{
+		{
+			description:   "get existing value from query params",
+			inputPath:     "ext.prebid.amp.data.amp-key",
+			expectedValue: json.RawMessage(`testAmpKey`),
+			expectedError: false,
+		},
+		{
+			description:   "get non-existing value from query params",
+			inputPath:     "ext.prebid.amp.data.amp-key-not-existing",
+			expectedValue: nil,
+			expectedError: true,
+		},
+		{
+			description:   "get value from not query params path",
+			inputPath:     "ext.data.amp-key-not-existing",
+			expectedValue: nil,
+			expectedError: false,
+		},
+	}
+
+	for _, test := range testCases {
+		res, err := getValueFromQueryParam(test.inputPath, params)
+
+		assert.Equal(t, test.expectedValue, res, "incorrect value found")
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestGetValueFromImp(t *testing.T) {
+
+	testCases := []struct {
+		description       string
+		inputPath         string
+		inputRequest      json.RawMessage
+		expectedTargeting map[string][]byte
+		expectedError     bool
+	}{
+
+		{
+			description:  "get existing value from impressions",
+			inputPath:    "imp.banner.h",
+			inputRequest: json.RawMessage(reqFullValid),
+			expectedTargeting: map[string][]byte{
+				"test_imp1": []byte(`350`),
+				"test_imp2": []byte(`360`),
+			},
+			expectedError: false,
+		},
+		{
+			description: "get existing value from impressions ext",
+			inputPath:   "imp.ext.appnexus.placementId",
+			expectedTargeting: map[string][]byte{
+				"test_imp1": []byte(`123`),
+				"test_imp2": []byte(`456`),
+			},
+			inputRequest:  json.RawMessage(reqFullValid),
+			expectedError: false,
+		},
+		{
+			description:       "get non-existing value from impressions",
+			inputPath:         "imp.video",
+			expectedTargeting: map[string][]byte(nil),
+			inputRequest:      json.RawMessage(reqFullValid),
+			expectedError:     true,
+		},
+		{
+			description:       "get value from invalid impressions",
+			inputPath:         "imp.video",
+			expectedTargeting: map[string][]byte(nil),
+			inputRequest:      json.RawMessage(reqFullInvalid),
+			expectedError:     true,
+		},
+		{
+			description:       "get value from request without impressions",
+			inputPath:         "imp.video",
+			expectedTargeting: map[string][]byte(nil),
+			inputRequest:      json.RawMessage(`{}`),
+			expectedError:     true,
+		},
+	}
+
+	for _, test := range testCases {
+
+		reqCache := requestCache{
+			resolvedReq: test.inputRequest,
+		}
+
+		resData, err := getValueFromImp(test.inputPath, &reqCache)
+
+		assert.Equal(t, test.expectedTargeting, resData, "incorrect imp data returned")
+
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestGetValueFromRequestJson(t *testing.T) {
+
+	testCases := []struct {
+		description   string
+		inputPath     string
+		expectedValue json.RawMessage
+		expectedError bool
+	}{
+
+		{
+			description:   "get existing value from req",
+			inputPath:     "site.page",
+			expectedValue: json.RawMessage(`test.com`),
+			expectedError: false,
+		},
+		{
+			description:   "get non-existing value from req",
+			inputPath:     "app",
+			expectedValue: json.RawMessage(nil),
+			expectedError: true,
+		},
+	}
+
+	reqCache := requestCache{
+		resolvedReq: json.RawMessage(reqFullValid),
+	}
+
+	for _, test := range testCases {
+		resValue, err := getDataFromRequestJson(test.inputPath, &reqCache)
+
+		assert.Equal(t, test.expectedValue, resValue, "incorrect request data returned")
+
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
diff --git a/adservertargeting/respdataprocessor_test.go b/adservertargeting/respdataprocessor_test.go
new file mode 100644
index 00000000..3490233d
--- /dev/null
+++ b/adservertargeting/respdataprocessor_test.go
@@ -0,0 +1,590 @@
+package adservertargeting
+
+import (
+	"encoding/json"
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/openrtb/v17/openrtb3"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+func TestProcessRequestTargetingData(t *testing.T) {
+
+	testCases := []struct {
+		description            string
+		inputAdServerTargeting adServerTargetingData
+		inputTargetingData     map[string]string
+		expectedTargetingData  map[string]string
+	}{
+
+		{
+			description: "Add single and targeting value by imp to not empty input targeting data",
+			inputAdServerTargeting: adServerTargetingData{
+				RequestTargetingData: map[string]RequestTargetingData{
+					"key1": {SingleVal: json.RawMessage(`val1`)},
+					"key2": {
+						TargetingValueByImpId: map[string][]byte{
+							"impId1": []byte(`impId1val`), "impId2": []byte(`impId2val`),
+						},
+					},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "key1": "val1", "key2": "impId1val",
+			},
+		},
+		{
+			description: "Add single and targeting value by imp to empty input targeting data",
+			inputAdServerTargeting: adServerTargetingData{
+				RequestTargetingData: map[string]RequestTargetingData{
+					"key1": {SingleVal: json.RawMessage(`val1`)},
+					"key2": {
+						TargetingValueByImpId: map[string][]byte{
+							"impId1": []byte(`impId1val`), "impId2": []byte(`impId2val`),
+						},
+					},
+				},
+			},
+			inputTargetingData: map[string]string{},
+			expectedTargetingData: map[string]string{
+				"key1": "val1", "key2": "impId1val",
+			},
+		},
+		{
+			description: "Add single value by imp to not empty input targeting data",
+			inputAdServerTargeting: adServerTargetingData{
+				RequestTargetingData: map[string]RequestTargetingData{
+					"key1": {SingleVal: json.RawMessage(`val1`)},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "key1": "val1",
+			},
+		},
+		{
+			description: "Add targeting value by imp to not empty input targeting data",
+			inputAdServerTargeting: adServerTargetingData{
+				RequestTargetingData: map[string]RequestTargetingData{
+					"key2": {
+						TargetingValueByImpId: map[string][]byte{
+							"impId1": []byte(`impId1val`), "impId2": []byte(`impId2val`),
+						},
+					},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "key2": "impId1val",
+			},
+		},
+	}
+
+	bidImpId := "impId1"
+	for _, test := range testCases {
+		processRequestTargetingData(&test.inputAdServerTargeting, test.inputTargetingData, bidImpId)
+		assert.Equal(t, test.expectedTargetingData, test.inputTargetingData, "incorrect targeting data")
+	}
+
+}
+
+func TestProcessResponseTargetingData(t *testing.T) {
+
+	testCases := []struct {
+		description            string
+		inputAdServerTargeting adServerTargetingData
+		inputTargetingData     map[string]string
+		inputBid               openrtb2.Bid
+		inputResponse          *openrtb2.BidResponse
+		inputSeatExt           json.RawMessage
+
+		expectedTargetingData map[string]string
+		expectedWarnings      []openrtb_ext.ExtBidderMessage
+	}{
+
+		{
+			description: "get value from seatbid.bid",
+			inputAdServerTargeting: adServerTargetingData{
+				ResponseTargetingData: []ResponseTargetingData{
+					{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "seatbid.bid.impid"},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1"},
+			inputResponse:      nil,
+			inputSeatExt:       nil,
+
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "bidderA_custom1": "testBidImpId1",
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage(nil),
+		},
+		{
+			description: "get value from seatbid.bid.ext.prebid.foo",
+			inputAdServerTargeting: adServerTargetingData{
+				ResponseTargetingData: []ResponseTargetingData{
+					{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "seatbid.bid.ext.prebid.foo"},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1"},
+			inputResponse:      nil,
+			inputSeatExt:       nil,
+
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "bidderA_custom1": "bar1",
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage(nil),
+		},
+		{
+			description: "get value from ext.testData.foo",
+			inputAdServerTargeting: adServerTargetingData{
+				ResponseTargetingData: []ResponseTargetingData{
+					{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "ext.testData.foo"},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1"},
+			inputResponse:      &openrtb2.BidResponse{Cur: "UAH", Ext: json.RawMessage(`{"testData": {"foo": "barExt"}}`)},
+			inputSeatExt:       nil,
+
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "bidderA_custom1": "barExt",
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage(nil),
+		},
+		{
+			description: "get value from resp",
+			inputAdServerTargeting: adServerTargetingData{
+				ResponseTargetingData: []ResponseTargetingData{
+					{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "cur"},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1"},
+			inputResponse:      &openrtb2.BidResponse{Cur: "UAH", Ext: json.RawMessage(`{"testData": {"foo": "barExt"}}`)},
+			inputSeatExt:       nil,
+
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "bidderA_custom1": "UAH",
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage(nil),
+		},
+		{
+			description: "get value from resp ext",
+			inputAdServerTargeting: adServerTargetingData{
+				ResponseTargetingData: []ResponseTargetingData{
+					{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "seatbid.ext.testData.foo"},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1"},
+			inputResponse:      nil,
+			inputSeatExt:       json.RawMessage(`{"testData": {"foo": "barBidderA"}}`),
+
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1", "bidderA_custom1": "barBidderA",
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage(nil),
+		},
+		{
+			description: "get value from resp ext with incorrect format",
+			inputAdServerTargeting: adServerTargetingData{
+				ResponseTargetingData: []ResponseTargetingData{
+					{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "seatbid.ext.testData"},
+				},
+			},
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1"},
+			inputResponse:      &openrtb2.BidResponse{Cur: "UAH", Ext: json.RawMessage(`{"testData": {"foo": "barExt"}}`)},
+			inputSeatExt:       json.RawMessage(`{"testData": {"foo": "barBidderA"}}`),
+
+			expectedTargetingData: map[string]string{
+				"inKey1": "inVal1",
+			},
+			expectedWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "incorrect value type for path: testData, value can only be string or number for bidder: bidderA, bid id: testBidId"},
+			},
+		},
+	}
+
+	bidderName := "bidderA"
+
+	inputBidsCache := bidsCache{
+		bids: map[string]map[string][]byte{
+			"bidderA": {"testBidId": []byte(`{"id":"testBidId","impid":"testBidImpId1","price":10,"cat":["cat11","cat12"],"ext":{"prebid":{"foo":"bar1"}}}`)},
+		},
+	}
+
+	for _, test := range testCases {
+		actualWarnings := processResponseTargetingData(&test.inputAdServerTargeting,
+			test.inputTargetingData, bidderName, test.inputBid, inputBidsCache,
+			test.inputResponse, test.inputSeatExt)
+		assert.Equal(t, test.expectedWarnings, actualWarnings, "incorrect warnings returned")
+		assert.Equal(t, test.expectedTargetingData, test.inputTargetingData, "incorrect targeting data")
+	}
+}
+
+func TestBuildBidExt(t *testing.T) {
+
+	testCases := []struct {
+		description             string
+		inputTargetingData      map[string]string
+		inputBid                openrtb2.Bid
+		inputWarnings           []openrtb_ext.ExtBidderMessage
+		truncateTargetAttribute int
+		expectedExt             json.RawMessage
+		expectedWarnings        []openrtb_ext.ExtBidderMessage
+	}{
+
+		{
+			description:        "build valid bid ext with existing warnings",
+			inputTargetingData: map[string]string{"inKey1": "inVal1"},
+			inputBid:           openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1", Ext: json.RawMessage(`{"prebid": {"test": 1}}`)},
+			inputWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "incorrect value type for path: testData, value can only be string or number for bidder: bidderA, bid id: testBidId"},
+			},
+			truncateTargetAttribute: 20,
+			expectedExt:             json.RawMessage(`{"prebid":{"targeting":{"inKey1":"inVal1"},"test":1}}`),
+			expectedWarnings: []openrtb_ext.ExtBidderMessage{
+				{Code: 10007, Message: "incorrect value type for path: testData, value can only be string or number for bidder: bidderA, bid id: testBidId"},
+			},
+		},
+		{
+			description:             "build valid bid ext without existing warnings",
+			inputTargetingData:      map[string]string{"inKey1": "inVal1"},
+			inputBid:                openrtb2.Bid{ID: "testBidId", ImpID: "testBidImpId1", Ext: json.RawMessage(`{"prebid": {"test": 1}}`)},
+			inputWarnings:           []openrtb_ext.ExtBidderMessage(nil),
+			truncateTargetAttribute: 20,
+			expectedExt:             json.RawMessage(`{"prebid":{"targeting":{"inKey1":"inVal1"},"test":1}}`),
+			expectedWarnings:        []openrtb_ext.ExtBidderMessage(nil),
+		},
+	}
+
+	for _, test := range testCases {
+		actualExt := buildBidExt(test.inputTargetingData, test.inputBid, test.inputWarnings, &test.truncateTargetAttribute)
+		assert.Equal(t, test.expectedWarnings, test.inputWarnings, "incorrect warnings returned")
+		assert.JSONEq(t, string(test.expectedExt), string(actualExt), "incorrect result extension")
+	}
+}
+
+func TestResolveKey(t *testing.T) {
+
+	testCases := []struct {
+		description                string
+		inputResponseTargetingData ResponseTargetingData
+		inputBidderName            string
+		expectedKey                string
+	}{
+
+		{
+			description:                "resolve key with macro",
+			inputResponseTargetingData: ResponseTargetingData{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "ext"},
+			inputBidderName:            "bidderA",
+			expectedKey:                "bidderA_custom1",
+		},
+		{
+			description:                "resolve key without macro",
+			inputResponseTargetingData: ResponseTargetingData{Key: "key_custom1", HasMacro: true, Path: "ext"},
+			inputBidderName:            "bidderA",
+			expectedKey:                "key_custom1",
+		},
+		{
+			description:                "resolve key with macro only",
+			inputResponseTargetingData: ResponseTargetingData{Key: "{{BIDDER}}", HasMacro: true, Path: "ext"},
+			inputBidderName:            "bidderA",
+			expectedKey:                "bidderA",
+		},
+		{
+			description:                "resolve key with macro and empty bidder name",
+			inputResponseTargetingData: ResponseTargetingData{Key: "{{BIDDER}}_custom1", HasMacro: true, Path: "ext"},
+			inputBidderName:            "",
+			expectedKey:                "_custom1",
+		},
+	}
+
+	for _, test := range testCases {
+		actualKey := resolveKey(test.inputResponseTargetingData, test.inputBidderName)
+		assert.Equal(t, test.expectedKey, actualKey, "incorrect resolved key")
+	}
+}
+
+func TestTruncateTargetingKeys(t *testing.T) {
+
+	testCases := []struct {
+		description             string
+		inputTargetingData      map[string]string
+		truncateTargetAttribute int
+		expectedTargetingData   map[string]string
+	}{
+
+		{
+			description:             "truncate targeting keys",
+			inputTargetingData:      map[string]string{"inKey1": "inVal1"},
+			truncateTargetAttribute: 5,
+			expectedTargetingData:   map[string]string{"inKey": "inVal1"},
+		},
+		{
+			description:             "do not truncate targeting keys",
+			inputTargetingData:      map[string]string{"inKey1": "inVal1"},
+			truncateTargetAttribute: 10,
+			expectedTargetingData:   map[string]string{"inKey1": "inVal1"},
+		},
+		{
+			description:             "exceed truncate limit",
+			inputTargetingData:      map[string]string{"inKey1": "inVal1"},
+			truncateTargetAttribute: 100,
+			expectedTargetingData:   map[string]string{"inKey1": "inVal1"},
+		},
+		{
+			description:             "limit long key to default length",
+			inputTargetingData:      map[string]string{"very_long_targeting_key_should_be_truncated_to_default": "inVal1"},
+			truncateTargetAttribute: 0,
+			expectedTargetingData:   map[string]string{"very_long_targeting_": "inVal1"},
+		},
+	}
+
+	for _, test := range testCases {
+		actualTargetingData := truncateTargetingKeys(test.inputTargetingData, &test.truncateTargetAttribute)
+		assert.Equal(t, test.expectedTargetingData, actualTargetingData, "incorrect targeting data")
+	}
+}
+
+func TestGetValueFromSeatBidBid(t *testing.T) {
+
+	testCases := []struct {
+		description   string
+		inputPath     string
+		expectedValue string
+		expectError   bool
+	}{
+
+		{
+			description:   "get existing valid value from bid",
+			inputPath:     "seatbid.bid.price",
+			expectedValue: "10",
+			expectError:   false,
+		},
+		{
+			description:   "get existing invalid value from bid",
+			inputPath:     "seatbid.bid.cat",
+			expectedValue: "",
+			expectError:   true,
+		},
+		{
+			description:   "get non-existing value from bid",
+			inputPath:     "seatbid.bid.test",
+			expectedValue: "",
+			expectError:   true,
+		},
+	}
+
+	inputBidsCache := bidsCache{
+		bids: map[string]map[string][]byte{
+			"bidderA": {"testBidId": []byte(`{"id":"testBidId","impid":"testBidImpId1","price":10,"cat":["cat11","cat12"],"ext":{"prebid":{"foo":"bar1"}}}`)},
+		},
+	}
+
+	for _, test := range testCases {
+		actualValue, actualErr := getValueFromSeatBidBid(test.inputPath, inputBidsCache, "bidderA", openrtb2.Bid{ID: "testBidId"})
+		assert.Equal(t, test.expectedValue, actualValue, "incorrect value returned")
+		if test.expectError {
+			assert.Error(t, actualErr, "unexpected error returned")
+		} else {
+			assert.NoError(t, actualErr, "expected error not returned")
+		}
+	}
+}
+
+func TestGetValueFromExt(t *testing.T) {
+
+	testCases := []struct {
+		description    string
+		inputPath      string
+		inputSeparator string
+		expectedValue  string
+		expectError    bool
+	}{
+
+		{
+			description:    "get existing valid value from bid.ext",
+			inputPath:      "seatbid.ext.prebid.foo",
+			inputSeparator: "seatbid.ext.",
+			expectedValue:  "bar1",
+			expectError:    false,
+		},
+		{
+			description:    "get non-existing valid value from bid.ext",
+			inputPath:      "seatbid.ext.prebid.foo1",
+			inputSeparator: "seatbid.ext.",
+			expectedValue:  "",
+			expectError:    true,
+		},
+		{
+			description:    "get existing valid with incorrect type value from bid.ext",
+			inputPath:      "seatbid.ext.prebid",
+			inputSeparator: "seatbid.ext.",
+			expectedValue:  "",
+			expectError:    true,
+		},
+		{
+			description:    "get existing valid with unexpected separator from bid.ext",
+			inputPath:      "seatbid.ext.prebid",
+			inputSeparator: ".ext.",
+			expectedValue:  "",
+			expectError:    true,
+		},
+	}
+	inputExt := json.RawMessage(`{"prebid":{"foo":"bar1"}}`)
+
+	for _, test := range testCases {
+		actualValue, actualErr := getValueFromExt(test.inputPath, test.inputSeparator, inputExt)
+		assert.Equal(t, test.expectedValue, actualValue, "incorrect value returned")
+		if test.expectError {
+			assert.Error(t, actualErr, "unexpected error returned")
+		} else {
+			assert.NoError(t, actualErr, "expected error not returned")
+		}
+	}
+}
+
+func TestGetValueFromResp(t *testing.T) {
+
+	testCases := []struct {
+		description   string
+		inputPath     string
+		inputResponse *openrtb2.BidResponse
+		expectedValue string
+		expectError   bool
+	}{
+
+		{
+			description:   "get existing valid value from response",
+			inputPath:     "cur",
+			inputResponse: &openrtb2.BidResponse{Cur: "UAH"},
+			expectedValue: "UAH",
+			expectError:   false,
+		},
+		{
+			description:   "get empty valid value from response",
+			inputPath:     "id",
+			inputResponse: &openrtb2.BidResponse{},
+			expectedValue: "",
+			expectError:   false,
+		},
+		{
+			description:   "get non-existing valid value from response",
+			inputPath:     "test",
+			inputResponse: &openrtb2.BidResponse{},
+			expectedValue: "",
+			expectError:   true,
+		},
+	}
+
+	for _, test := range testCases {
+		actualValue, actualErr := getValueFromResp(test.inputPath, test.inputResponse)
+		assert.Equal(t, test.expectedValue, actualValue, "incorrect value returned")
+		if test.expectError {
+			assert.Error(t, actualErr, "unexpected error returned")
+		} else {
+			assert.NoError(t, actualErr, "expected error not returned")
+		}
+	}
+}
+
+func TestGetRespData(t *testing.T) {
+
+	nbr := openrtb3.NoBidProxy
+	testCases := []struct {
+		description   string
+		inputField    string
+		inputResponse *openrtb2.BidResponse
+		expectedValue string
+		expectError   bool
+	}{
+		{
+			description:   "get id from response",
+			inputField:    "id",
+			inputResponse: &openrtb2.BidResponse{ID: "testId"},
+			expectedValue: "testId",
+			expectError:   false,
+		},
+		{
+			description:   "get bidid from response",
+			inputField:    "bidid",
+			inputResponse: &openrtb2.BidResponse{BidID: "testBidId"},
+			expectedValue: "testBidId",
+			expectError:   false,
+		},
+		{
+			description:   "get cur from response",
+			inputField:    "cur",
+			inputResponse: &openrtb2.BidResponse{Cur: "UAH"},
+			expectedValue: "UAH",
+			expectError:   false,
+		},
+		{
+			description:   "get customdata from response",
+			inputField:    "customdata",
+			inputResponse: &openrtb2.BidResponse{CustomData: "testCustomdata"},
+			expectedValue: "testCustomdata",
+			expectError:   false,
+		},
+		{
+			description:   "get nbr from response",
+			inputField:    "nbr",
+			inputResponse: &openrtb2.BidResponse{NBR: &nbr},
+			expectedValue: "5",
+			expectError:   false,
+		},
+		{
+			description:   "get non-existing value from response",
+			inputField:    "test",
+			inputResponse: &openrtb2.BidResponse{ID: "testId"},
+			expectedValue: "",
+			expectError:   true,
+		},
+	}
+
+	for _, test := range testCases {
+		actualValue, actualErr := getRespData(test.inputResponse, test.inputField)
+		assert.Equal(t, test.expectedValue, actualValue, "incorrect value returned")
+		if test.expectError {
+			assert.Error(t, actualErr, "unexpected error returned")
+		} else {
+			assert.NoError(t, actualErr, "expected error not returned")
+		}
+	}
+
+}
+
+func TestResponseObjectStructure(t *testing.T) {
+	// in case BidResponse format will change in next versions this test will show the error
+	// current implementation is up to date with OpenRTB 2.5 and OpenRTB 2.6 formats
+	fieldsToCheck := map[string]reflect.Kind{
+		"id":         reflect.String,
+		"bidid":      reflect.String,
+		"cur":        reflect.String,
+		"customdata": reflect.String,
+		"nbr":        reflect.Pointer,
+	}
+
+	tt := reflect.TypeOf(openrtb2.BidResponse{})
+	fields := reflect.VisibleFields(tt)
+
+	for fieldName, fieldType := range fieldsToCheck {
+		fieldFound := false
+		for _, field := range fields {
+			if fieldName == strings.ToLower(field.Name) {
+				fieldFound = true
+				assert.Equal(t, fieldType, field.Type.Kind(), "incorrect type for field: %s", fieldName)
+				break
+			}
+		}
+		assert.True(t, fieldFound, "field %s is not found in bidResponse object", fieldName)
+	}
+}
diff --git a/adservertargeting/test_data.go b/adservertargeting/test_data.go
new file mode 100644
index 00000000..4ee3be53
--- /dev/null
+++ b/adservertargeting/test_data.go
@@ -0,0 +1,370 @@
+package adservertargeting
+
+const (
+	reqValid = `{
+  "id": "req_id",
+  "imp": [
+    {
+      "id": "test_imp1",
+      "ext": {"appnexus": {"placementId": 250419771}},
+      "banner": {"format": [{"h": 250, "w": 300}]}
+    },
+    {
+      "id": "test_imp2",
+      "ext": {"appnexus": {"placementId": 250419771}},
+      "banner": {"format": [{"h": 250, "w": 300}]}
+    }
+  ],
+  "site": {"page": "test.com"}
+}`
+
+	reqInvalid = `{
+  "id": "req_id",
+  "imp": {
+	 "incorrect":true
+   },
+  "site": {"page": "test.com"}
+}`
+
+	reqNoImps = `{
+  "id": "req_id",
+  "site": {"page": "test.com"}
+}`
+
+	testUrl = "https://www.test-url.com?amp-key=testAmpKey&data-override-height=400"
+
+	reqFullValid = `{
+  "id": "req_id",
+  "imp": [
+    {
+      "id": "test_imp1",
+      "ext": {"appnexus": {"placementId": 123}},
+      "banner": {"format": [{"h": 250, "w": 300}], "w": 260, "h": 350}
+    },
+    {
+      "id": "test_imp2",
+      "ext": {"appnexus": {"placementId": 456}},
+      "banner": {"format": [{"h": 400, "w": 600}], "w": 270, "h": 360}
+    }
+  ],
+  "site": {"page": "test.com"}
+}`
+
+	reqFullInvalid = `{
+  "imp": [
+    {
+      "ext": {"appnexus": {"placementId": 123}}
+    },
+    {
+      "ext": {"appnexus": {"placementId": 456}}
+    }
+  ]
+}`
+
+	reqExt = `{
+  "prebid": {
+    "adservertargeting": [
+      {
+        "key": "hb_amp_param",
+        "source": "bidrequest",
+        "value": "ext.prebid.amp.data.ampkey"
+      },
+      {
+        "key": "hb_req_imp_ext_param",
+        "source": "bidrequest",
+        "value": "imp.ext.prebid.test"
+      },
+      {
+        "key": "hb_req_imp_ext_bidder_param",
+        "source": "bidrequest",
+        "value": "imp.ext.bidder1.tagid"
+      },
+      {
+        "key": "hb_req_imp_param",
+        "source": "bidrequest",
+        "value": "imp.bidfloor"
+      },
+      {
+        "key": "hb_req_ext_param",
+        "source": "bidrequest",
+        "value": "ext.prebid.targeting.includebrandcategory"
+      },
+	  {
+        "key": "hb_req_user_param",
+        "source": "bidrequest",
+        "value": "user.yob"
+      },
+      {
+        "key": "hb_static_thing",
+        "source": "static",
+        "value": "test-static-value"
+      },
+      {
+        "key": "{{BIDDER}}_custom1",
+        "source": "bidresponse",
+        "value": "seatbid.bid.ext.custom1"
+      },
+      {
+        "key": "custom2",
+        "source": "bidresponse",
+        "value": "seatbid.bid.ext.custom2"
+      },
+      {
+        "key": "{{BIDDER}}_imp",
+        "source": "bidresponse",
+        "value": "seatbid.bid.impid"
+      },
+      {
+        "key": "seat_cur",
+        "source": "bidresponse",
+        "value": "cur"
+      }
+    ],
+    "targeting": {
+      "includewinners": false,
+      "includebidderkeys": true,
+      "includebrandcategory": {
+        "primaryadserver": 1,
+        "publisher": "",
+        "withcategory": true
+      }
+    }
+  }
+}`
+
+	seatBid0Bid0Ext = `{
+  "prebid": {
+    "foo": "bar1",
+    "targeting": {
+      "appnexus_custom6": "true",
+      "custom2": "bar1",
+      "custom3": "10",
+      "custom4": "barApn",
+      "custom5": "USD",
+      "custom6":"testResponse", 
+      "custom7":"testBidId", 
+      "custom8":"testCustomData", 
+      "custom9":"2",
+      "hb_amp_param": "testAmpKey",
+      "hb_imp_param": "111"
+    }
+  }
+}`
+
+	seatBid0Bid1Ext = `
+{
+  "prebid": {
+    "foo": "bar2",
+    "targeting": {
+      "appnexus_custom6": "true",
+      "custom2": "bar2",
+      "custom3": "20",
+      "custom4": "barApn",
+      "custom5": "USD",
+      "custom6":"testResponse", 
+      "custom7":"testBidId", 
+      "custom8":"testCustomData", 
+      "custom9":"2",
+      "hb_amp_param": "testAmpKey",
+      "hb_imp_param": "222"
+    }
+  }
+}`
+	seatBid0Bid2Ext = `
+{
+  "prebid": {
+    "foo": "bar3",
+    "targeting": {
+      "appnexus_custom6": "true",
+      "custom2": "bar3",
+      "custom3": "30",
+      "custom4": "barApn",
+      "custom5": "USD",
+      "custom6":"testResponse", 
+      "custom7":"testBidId", 
+      "custom8":"testCustomData", 
+      "custom9":"2",
+      "hb_amp_param": "testAmpKey",
+      "hb_imp_param": "333"
+    }
+  }
+}`
+
+	seatBid1Bid0Ext = `{
+  "prebid": {
+    "foo": "bar11",
+    "targeting": {
+      "custom2": "bar11",
+      "custom3": "11",
+      "custom4": "barRubicon",
+      "custom5": "USD",
+      "custom6":"testResponse", 
+      "custom7":"testBidId", 
+      "custom8":"testCustomData", 
+      "custom9":"2",
+      "hb_amp_param": "testAmpKey",
+      "hb_imp_param": "111",
+      "rubicon_custom6": "true",
+      "testInput": 111
+    }
+  }
+}`
+	seatBid1Bid1Ext = `
+{
+  "prebid": {
+    "foo": "bar22",
+    "targeting": {
+      "custom2": "bar22",
+      "custom3": "22",
+      "custom4": "barRubicon",
+      "custom5": "USD",
+      "custom6":"testResponse", 
+      "custom7":"testBidId", 
+      "custom8":"testCustomData", 
+      "custom9":"2",
+      "hb_amp_param": "testAmpKey",
+      "hb_imp_param": "222",
+      "rubicon_custom6": "true",
+      "testInput": 222
+    }
+  }
+}`
+
+	seatBid1Bid2Ext = `{
+  "prebid": {
+    "foo": "bar33",
+    "targeting": {
+      "custom2": "bar33",
+      "custom3": "33",
+      "custom4": "barRubicon",
+      "custom5": "USD",
+      "custom6":"testResponse", 
+      "custom7":"testBidId", 
+      "custom8":"testCustomData", 
+      "custom9":"2",
+      "hb_amp_param": "testAmpKey",
+      "hb_imp_param": "333",
+      "rubicon_custom6": "true",
+      "testInput": 333
+    }
+  }
+}`
+
+	bid0Ext = `{"prebid": {"foo":"bar1", "targeting": {"custom_attr":"bar1"}}}`
+	bid1Ext = `{"prebid": {"foo":"bar2", "targeting": {"custom_attr":"bar2"}}}`
+	bid2Ext = `{"prebid": {"foo":"bar3", "targeting": {"custom_attr":"bar3"}}}`
+
+	apnBid0Ext = `{
+  "custom1": 1111,
+  "custom2": "a1111",
+  "prebid": {
+    "foo": "bar1",
+    "targeting": {
+      "appnexus_custom1": "1111",
+      "appnexus_imp":"imp1",
+      "custom2": "a1111",
+      "hb_amp_param": "testAmpKey",
+      "hb_req_imp_ext_bidde": "111",
+      "hb_req_imp_param": "10",
+      "hb_req_user_param": "2000",
+      "hb_static_thing": "test-static-value",
+      "seat_cur":"USD"
+    }
+  }
+}`
+
+	apnBid1Ext = `{
+  "custom1": 2222,
+  "custom2": "a2222",
+  "prebid": {
+    "foo": "bar2",
+    "targeting": {
+      "appnexus_custom1": "2222",
+      "appnexus_imp":"imp2",
+      "custom2": "a2222",
+      "hb_amp_param": "testAmpKey",
+      "hb_req_imp_ext_bidde": "222",
+      "hb_req_imp_param": "20",
+      "hb_req_user_param": "2000",
+      "hb_static_thing": "test-static-value",
+      "seat_cur":"USD"
+    }
+  }
+}`
+	apnBid2Ext = `{
+  "custom1": 3333,
+  "custom2": "a3333",
+  "prebid": {
+    "foo": "bar3",
+    "targeting": {
+      "appnexus_custom1": "3333",
+      "appnexus_imp":"imp3",
+      "custom2": "a3333",
+      "hb_amp_param": "testAmpKey",
+      "hb_req_imp_ext_bidde": "333",
+      "hb_req_imp_param": "30",
+      "hb_req_user_param": "2000",
+      "hb_static_thing": "test-static-value",
+      "seat_cur":"USD"
+    }
+  }
+}`
+
+	rbcBid0Ext = `{
+  "custom1": 4444,
+  "custom2": "r4444",
+  "prebid": {
+    "foo": "bar11",
+    "targeting": {
+      "custom2": "r4444",
+      "rubicon_imp":"imp1",
+      "hb_amp_param": "testAmpKey",
+      "hb_req_imp_ext_bidde": "111",
+      "hb_req_imp_param": "10",
+      "hb_req_user_param": "2000",
+      "hb_static_thing": "test-static-value",
+      "rubicon_custom1": "4444",
+      "testInput": 111,
+      "seat_cur":"USD"
+    }
+  }
+}`
+	rbcBid1Ext = `{
+  "custom1": 5555,
+  "custom2": "r5555",
+  "prebid": {
+    "foo": "bar22",
+    "targeting": {
+      "custom2": "r5555",
+      "rubicon_imp":"imp2",
+      "hb_amp_param": "testAmpKey",
+      "hb_req_imp_ext_bidde": "222",
+      "hb_req_imp_param": "20",
+      "hb_req_user_param": "2000",
+      "hb_static_thing": "test-static-value",
+      "rubicon_custom1": "5555",
+      "testInput": 222,
+      "seat_cur":"USD"
+    }
+  }
+}`
+	rbcBid2Ext = `{
+  "custom1": 6666,
+  "custom2": "r6666",
+  "prebid": {
+    "foo": "bar33",
+    "targeting": {
+      "custom2": "r6666",
+      "rubicon_imp":"imp3",
+      "hb_amp_param": "testAmpKey",
+      "hb_req_imp_ext_bidde": "333",
+      "hb_req_imp_param": "30",
+      "hb_req_user_param": "2000",
+      "hb_static_thing": "test-static-value",
+      "rubicon_custom1": "6666",
+      "testInput": 333,
+      "seat_cur":"USD"
+    }
+  }
+}`
+)
diff --git a/adservertargeting/utils_test.go b/adservertargeting/utils_test.go
new file mode 100644
index 00000000..cc403ddf
--- /dev/null
+++ b/adservertargeting/utils_test.go
@@ -0,0 +1,164 @@
+package adservertargeting
+
+import (
+	"github.com/buger/jsonparser"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestSplitAndGet(t *testing.T) {
+
+	testCases := []struct {
+		description   string
+		inputPath     string
+		inputData     []byte
+		inputDelim    string
+		expectedValue string
+		expectedError bool
+	}{
+		{
+			description:   "get value from valid input",
+			inputData:     []byte(`{"site": {"page": "test.com"}}`),
+			inputPath:     "site.page",
+			inputDelim:    ".",
+			expectedValue: "test.com",
+			expectedError: false,
+		},
+		{
+			description:   "get value from invalid input",
+			inputData:     nil,
+			inputPath:     "site.page",
+			inputDelim:    ".",
+			expectedValue: "",
+			expectedError: true,
+		},
+	}
+
+	for _, test := range testCases {
+		res, err := splitAndGet(test.inputPath, test.inputData, test.inputDelim)
+
+		assert.Equal(t, test.expectedValue, res, "incorrect result")
+
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestVerifyPrefixAndTrimFound(t *testing.T) {
+
+	testCases := []struct {
+		description   string
+		inputPath     string
+		inputPrefix   string
+		expectedValue string
+		valueFound    bool
+	}{
+		{
+			description:   "get existing value ",
+			inputPath:     "site.page.id",
+			inputPrefix:   "site.page.",
+			expectedValue: "id",
+			valueFound:    true,
+		},
+		{
+			description:   "get non-existing value",
+			inputPath:     "site.page.id",
+			inputPrefix:   "incorrect",
+			expectedValue: "",
+			valueFound:    false,
+		},
+	}
+
+	for _, test := range testCases {
+		res, found := verifyPrefixAndTrim(test.inputPath, test.inputPrefix)
+		assert.Equal(t, test.valueFound, found, "found value incorrect")
+		assert.Equal(t, test.expectedValue, res, "incorrect returned result")
+	}
+}
+
+func TestTypedLookup(t *testing.T) {
+
+	testCases := []struct {
+		description   string
+		inputData     []byte
+		inputPath     string
+		inputKeys     []string
+		expectedValue []byte
+		expectedError bool
+	}{
+		{
+			description:   "lookup existing value",
+			inputData:     []byte(`{"site": {"page": "test.com"}}`),
+			inputPath:     "site.page",
+			inputKeys:     []string{"site", "page"},
+			expectedValue: []byte(`test.com`),
+			expectedError: false,
+		},
+		{
+			description:   "lookup non-existing value",
+			inputData:     []byte(`{"site": {"page": "test.com"}}`),
+			inputPath:     "site.page",
+			inputKeys:     []string{"site", "id"},
+			expectedValue: []byte(nil),
+			expectedError: true,
+		},
+		{
+			description:   "lookup value in incorrect json",
+			inputData:     []byte(`[`),
+			inputPath:     "site.page",
+			inputKeys:     []string{"site", "page"},
+			expectedValue: []byte(nil),
+			expectedError: true,
+		},
+		{
+			description:   "lookup object value",
+			inputData:     []byte(`{"site": {"page": "test.com"}}`),
+			inputPath:     "site",
+			inputKeys:     []string{"site"},
+			expectedValue: []byte(nil),
+			expectedError: true,
+		},
+	}
+
+	for _, test := range testCases {
+
+		res, err := typedLookup(test.inputData, test.inputPath, test.inputKeys...)
+
+		assert.Equal(t, test.expectedValue, res, "incorrect returned result")
+
+		if test.expectedError {
+			assert.Error(t, err, "expected error not returned")
+		} else {
+			assert.NoError(t, err, "unexpected error returned")
+		}
+	}
+}
+
+func TestVerifyType(t *testing.T) {
+
+	testCases := []struct {
+		description string
+		inputValue  jsonparser.ValueType
+		expectedRes bool
+	}{
+		{
+			description: "verify correct value",
+			inputValue:  jsonparser.String,
+			expectedRes: true,
+		},
+		{
+			description: "verify correct value",
+			inputValue:  jsonparser.Object,
+			expectedRes: false,
+		},
+	}
+
+	for _, test := range testCases {
+		correctType := verifyType(test.inputValue)
+		assert.Equal(t, test.expectedRes, correctType, "incorrect verified type result")
+
+	}
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 5b125fc5..a8e95e4d 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/prebid/prebid-server/analytics"
 	analyticsConf "github.com/prebid/prebid-server/analytics/config"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
@@ -398,8 +399,9 @@ func TestOverrideWithParams(t *testing.T) {
 		bidRequest *openrtb2.BidRequest
 	}
 	type testOutput struct {
-		bidRequest *openrtb2.BidRequest
-		errorMsgs  []string
+		bidRequest        *openrtb2.BidRequest
+		errorMsgs         []string
+		expectFatalErrors bool
 	}
 	testCases := []struct {
 		desc     string
@@ -520,7 +522,8 @@ func TestOverrideWithParams(t *testing.T) {
 					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
 					User: &openrtb2.User{Ext: json.RawMessage(`malformed`)},
 				},
-				errorMsgs: []string{"invalid character 'm' looking for beginning of value"},
+				errorMsgs:         []string{"invalid character 'm' looking for beginning of value"},
+				expectFatalErrors: true,
 			},
 		},
 		{
@@ -567,7 +570,8 @@ func TestOverrideWithParams(t *testing.T) {
 					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
 					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
 				},
-				errorMsgs: []string{"invalid character 'm' looking for beginning of object key string"},
+				errorMsgs:         []string{"invalid character 'm' looking for beginning of object key string"},
+				expectFatalErrors: true,
 			},
 		},
 	}
@@ -579,6 +583,7 @@ func TestOverrideWithParams(t *testing.T) {
 		assert.Len(t, errs, len(test.expected.errorMsgs), test.desc)
 		if len(test.expected.errorMsgs) > 0 {
 			assert.Equal(t, test.expected.errorMsgs[0], errs[0].Error(), test.desc)
+			assert.Equal(t, test.expected.expectFatalErrors, errortypes.ContainsFatalError(errs), test.desc)
 		}
 	}
 }
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 43dac63d..41debbbe 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -108,6 +108,10 @@ func TestJsonSampleRequests(t *testing.T) {
 			"Assert we correctly validate request-defined custom currency rates when present in root.ext",
 			"currency-conversion/custom-rates/errors",
 		},
+		{
+			"Assert request with ad server targeting is processing correctly",
+			"adservertargeting",
+		},
 	}
 
 	for _, tc := range testSuites {
@@ -183,6 +187,38 @@ func runTestCase(t *testing.T, auctionEndpointHandler httprouter.Handle, test te
 	}
 }
 
+func compareWarnings(t *testing.T, expectedBidResponseExt, actualBidResponseExt []byte, warnPath string) {
+	expectedWarnings, _, _, err := jsonparser.Get(expectedBidResponseExt, warnPath)
+	if err != nil && err != jsonparser.KeyPathNotFoundError {
+		assert.Fail(t, "error getting data from response extension")
+	}
+	if len(expectedWarnings) > 0 {
+		actualWarnings, _, _, err := jsonparser.Get(actualBidResponseExt, warnPath)
+		if err != nil && err != jsonparser.KeyPathNotFoundError {
+			assert.Fail(t, "error getting data from response extension")
+		}
+
+		var expectedWarn []openrtb_ext.ExtBidderMessage
+		err = json.Unmarshal(expectedWarnings, &expectedWarn)
+		if err != nil {
+			assert.Fail(t, "error unmarshalling expected warnings data from response extension")
+		}
+
+		var actualWarn []openrtb_ext.ExtBidderMessage
+		err = json.Unmarshal(actualWarnings, &actualWarn)
+		if err != nil {
+			assert.Fail(t, "error unmarshalling actual warnings data from response extension")
+		}
+
+		// warnings from different bidders may be returned in different order.
+		assert.Equal(t, len(expectedWarn), len(actualWarn), "incorrect warnings number")
+		for i, expWarn := range expectedWarn {
+			actualWarning := actualWarn[i]
+			assert.Contains(t, actualWarning.Message, expWarn.Message, "incorrect warning")
+		}
+	}
+}
+
 // Once unmarshalled, bidResponse objects can't simply be compared with an `assert.Equalf()` call
 // because tests fail if the elements inside the `bidResponse.SeatBid` and `bidResponse.SeatBid.Bid`
 // arrays, if any, are not listed in the exact same order in the actual version and in the expected version.
@@ -192,6 +228,10 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o
 	assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile)
 	assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile)
 
+	if len(expectedBidResponse.Ext) > 0 {
+		compareWarnings(t, expectedBidResponse.Ext, actualBidResponse.Ext, "warnings.general")
+	}
+
 	//Assert []SeatBid and their Bid elements independently of their order
 	assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid is expected to contain %d elements but contains %d. Test: %s\n", len(expectedBidResponse.SeatBid), len(actualBidResponse.SeatBid), testFile)
 
@@ -235,6 +275,10 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o
 			}
 			assert.Equalf(t, expectedBid.ImpID, actualBidMap[bidID].ImpID, "BidResponse.SeatBid[%s].Bid[%s].ImpID doesn't match expected. Test: %s\n", bidderName, bidID, testFile)
 			assert.Equalf(t, expectedBid.Price, actualBidMap[bidID].Price, "BidResponse.SeatBid[%s].Bid[%s].Price doesn't match expected. Test: %s\n", bidderName, bidID, testFile)
+
+			if len(expectedBid.Ext) > 0 {
+				assert.JSONEq(t, string(expectedBid.Ext), string(actualBidMap[bidID].Ext), "Incorrect bid extension")
+			}
 		}
 	}
 }
@@ -1074,7 +1118,7 @@ func TestStoredRequests(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testStoreVideoAttr := []bool{true, true, false, false, false}
@@ -1447,7 +1491,7 @@ func TestValidateRequest(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testCases := []struct {
@@ -1979,7 +2023,7 @@ func TestSetIntegrationType(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testCases := []struct {
@@ -2044,7 +2088,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	req := &openrtb2.BidRequest{}
@@ -2147,7 +2191,7 @@ func TestOversizedRequest(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -2185,7 +2229,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -2521,7 +2565,7 @@ func TestValidateImpExt(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	for _, group := range testGroups {
@@ -2573,7 +2617,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2620,7 +2664,7 @@ func TestCCPAInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2671,7 +2715,7 @@ func TestNoSaleInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2725,7 +2769,7 @@ func TestValidateSourceTID(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -2769,7 +2813,7 @@ func TestSChainInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -3264,7 +3308,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	ui := int64(1)
@@ -3573,7 +3617,7 @@ func TestAuctionWarnings(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
@@ -3618,12 +3662,14 @@ func TestParseRequestParseImpInfoError(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
+	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
 
-	resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+	resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 	assert.Nil(t, resReq, "Result request should be nil due to incorrect imp")
 	assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp")
@@ -4193,12 +4239,14 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				empty_fetcher.EmptyFetcher{},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			assert.NoError(t, resReq.RebuildRequest())
 
@@ -4296,12 +4344,14 @@ func TestParseRequestStoredResponses(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				&mockStoredResponseFetcher{mockStoredResponses},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			_, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			_, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			if test.expectedErrorCount == 0 {
 				assert.Equal(t, test.expectedStoredResponses, storedResponses, "stored responses should match")
@@ -4384,11 +4434,13 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				&mockStoredResponseFetcher{mockStoredBidResponses},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
-			_, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			_, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			if test.expectedErrorCount == 0 {
 				assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match")
@@ -4418,7 +4470,7 @@ func TestValidateStoredResp(t *testing.T) {
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		&mockStoredResponseFetcher{},
-		hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+		hooks.EmptyPlanBuilder{},
 	}
 
 	testCases := []struct {
@@ -5225,12 +5277,14 @@ func TestParseRequestMultiBid(t *testing.T) {
 				nil,
 				hardcodedResponseIPValidator{response: true},
 				empty_fetcher.EmptyFetcher{},
-				hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
+				hooks.EmptyPlanBuilder{},
 			}
 
+			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
+
 			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
 
-			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})
+			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
 
 			assert.NoError(t, resReq.RebuildRequest())
 
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 2eb3fcd3..703cc909 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -16,7 +16,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
-	"github.com/prebid/prebid-server/hooks/hookexecution"
+	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -1224,7 +1224,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m
 		nil,
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 	return deps, metrics, mockModule
 }
@@ -1269,7 +1269,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		regexp.MustCompile(`[<>]`),
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 }
 
@@ -1292,7 +1292,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames)
 		regexp.MustCompile(`[<>]`),
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 
 	return deps
@@ -1317,7 +1317,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
 		regexp.MustCompile(`[<>]`),
 		hardcodedResponseIPValidator{response: true},
 		empty_fetcher.EmptyFetcher{},
-		&hookexecution.EmptyHookExecutor{},
+		hooks.EmptyPlanBuilder{},
 	}
 
 	return edep
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index d98b1e11..b735226b 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -212,6 +212,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
 				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
+				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -223,6 +224,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
 				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
+				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -235,6 +237,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"appnexus":       `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
+				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -247,6 +250,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"appnexus":       `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
+				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 	}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index dff15354..5dde9f9b 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -1710,8 +1710,6 @@ func TestBidResponseCurrency(t *testing.T) {
 				ID:      "some-request-id",
 				SeatBid: sampleSeatBid,
 				Cur:     "USD",
-				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
-`),
 			},
 		},
 		{
@@ -1726,8 +1724,6 @@ func TestBidResponseCurrency(t *testing.T) {
 				ID:      "some-request-id",
 				SeatBid: emptySeatBid,
 				Cur:     "",
-				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
-`),
 			},
 		},
 		{
@@ -1742,8 +1738,6 @@ func TestBidResponseCurrency(t *testing.T) {
 				ID:      "some-request-id",
 				SeatBid: sampleSeatBid,
 				Cur:     "",
-				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
-`),
 			},
 		},
 		{
@@ -1758,8 +1752,6 @@ func TestBidResponseCurrency(t *testing.T) {
 				ID:      "some-request-id",
 				SeatBid: emptySeatBid,
 				Cur:     "",
-				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
-`),
 			},
 		},
 	}
@@ -3938,7 +3930,7 @@ func TestMakeBidExtJSON(t *testing.T) {
 			origbidcpm:         10.0000,
 			origbidcur:         "USD",
 			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
-			expectedBidExt:     `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":""},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
+			expectedBidExt:     `{"prebid":{"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
 			expectedErrMessage: "",
 		},
 		{
diff --git a/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json
new file mode 100644
index 00000000..627c95f9
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-amp-imp-ext-one-prebid-bidder.json
@@ -0,0 +1,133 @@
+{
+    "requestType": "amp",
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [
+                {
+                    "id": "some-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 600,
+                                "h": 500
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                }
+                            }
+                        },
+                        "data": {
+                            "keywords": "prebid server example"
+                        },
+                        "context": {
+                            "data": {
+                                "keywords": "another prebid server example"
+                            }
+                        }
+                    }
+                }
+            ]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                },
+                                "data": {
+                                    "keywords": "prebid server example"
+                                },
+                                "context": {
+                                    "data": {
+                                        "keywords": "another prebid server example"
+                                    }
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "some-imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 500,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "banner"
+                            }
+                        ],
+                        "seat": "appnexus"
+                    }
+                ]
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "some-request-id",
+            "seatbid": [
+                {
+                    "seat": "appnexus",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "some-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 500,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "banner"
+                                }
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/request-multi-bidders-debug-info.json b/exchange/exchangetest/request-multi-bidders-debug-info.json
index 368c6e45..8f41286b 100644
--- a/exchange/exchangetest/request-multi-bidders-debug-info.json
+++ b/exchange/exchangetest/request-multi-bidders-debug-info.json
@@ -154,7 +154,6 @@
               "ext": {
                 "origbidcpm": 12.00,
                 "prebid": {
-                  "type": "",
                   "targeting": {
                     "hb_bidder": "appnexus",
                     "hb_bidder_appnexus": "appnexus",
diff --git a/exchange/exchangetest/request-multi-bidders-one-no-resp.json b/exchange/exchangetest/request-multi-bidders-one-no-resp.json
index 8ec9034a..c1fe595a 100644
--- a/exchange/exchangetest/request-multi-bidders-one-no-resp.json
+++ b/exchange/exchangetest/request-multi-bidders-one-no-resp.json
@@ -126,7 +126,6 @@
               "ext": {
                 "origbidcpm": 12.00,
                 "prebid": {
-                  "type": "",
                   "targeting": {
                     "hb_bidder": "appnexus",
                     "hb_bidder_appnexus": "appnexus",