Repository: prebid/prebid-server
Pull request #2670 (2023-03-28 11:01:40)

Oftmedia: Remove 152Media / OFTMedia alias
Remove OFTMedia alias

SyntaxNode: FYI: This is a request we (Xandr) received from 152 Media. We are proceeding on their behalf.
onkarvhanumante: > > Should also create docs PR to remove
> > 
> > * prebid.github.io/dev-docs/bidders/oftmedia_server.md
> > * prebid.github.io/dev-docs/bidders/oftmedia.md
> 
> Please do not remove `prebid.github.io/dev-docs/bidders/oftmedia.md`. That is the for the PrebidJS alias of AppNexus which is remaining.

Thanks @SyntaxNode 


Patch:
diff --git a/adapters/limelightDigital/limelightDigital.go b/adapters/limelightDigital/limelightDigital.go
index fd162fa3..0fa7496c 100644
--- a/adapters/limelightDigital/limelightDigital.go
+++ b/adapters/limelightDigital/limelightDigital.go
@@ -155,12 +155,8 @@ func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtLimelightDigital, e
 }
 
 func (a *adapter) buildEndpointURL(params *openrtb_ext.ImpExtLimelightDigital) (string, error) {
-	firstDotIndex := strings.IndexByte(params.Host, '.')
-	if firstDotIndex < 1 || firstDotIndex == (len(params.Host)-1) {
-		return "", fmt.Errorf("hostname is invalid: %s", params.Host)
-	}
 	endpointParams := macros.EndpointTemplateParams{
-		Host:        params.Host[:firstDotIndex],
+		Host:        params.Host,
 		PublisherID: params.PublisherID.String(),
 	}
 	return macros.ResolveMacros(a.endpointTemplate, endpointParams)
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/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..d0d2bcf6 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -234,6 +234,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		BidderImpReplaceImpID:      bidderImpReplaceImp,
 		PubID:                      labels.PubID,
 		HookExecutor:               deps.hookExecutor,
+		QueryParams:                r.URL.Query(),
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
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/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 d3536a1b..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"
@@ -247,6 +248,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderEPlanning:         eplanning.Builder,
 		openrtb_ext.BidderEpom:              epom.Builder,
 		openrtb_ext.BidderEVolution:         evolution.Builder,
+		openrtb_ext.BidderEvtech:            limelightDigital.Builder,
 		openrtb_ext.BidderFreewheelSSP:      freewheelssp.Builder,
 		openrtb_ext.BidderFreewheelSSPOld:   freewheelssp.Builder,
 		openrtb_ext.BidderGamma:             gamma.Builder,
@@ -290,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,
@@ -302,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..aeba357f 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
 		}
@@ -436,6 +438,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 +835,6 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 
 	bidResponse.SeatBid = seatBids
 
-	bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt)
-
 	return bidResponse, err
 }
 
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 a8a7db83..171d31bb 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -160,6 +160,7 @@ const (
 	BidderEPlanning         BidderName = "eplanning"
 	BidderEpom              BidderName = "epom"
 	BidderEVolution         BidderName = "e_volution"
+	BidderEvtech            BidderName = "evtech"
 	BidderFreewheelSSP      BidderName = "freewheelssp"
 	BidderFreewheelSSPOld   BidderName = "freewheel-ssp"
 	BidderGamma             BidderName = "gamma"
@@ -203,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"
@@ -215,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"
@@ -343,6 +344,7 @@ func CoreBidderNames() []BidderName {
 		BidderEPlanning,
 		BidderEpom,
 		BidderEVolution,
+		BidderEvtech,
 		BidderFreewheelSSP,
 		BidderFreewheelSSPOld,
 		BidderGamma,
@@ -386,7 +388,6 @@ func CoreBidderNames() []BidderName {
 		BidderNextMillennium,
 		BidderNinthDecimal,
 		BidderNoBid,
-		BidderOFTMedia,
 		BidderOneTag,
 		BidderOpenWeb,
 		BidderOpenx,
@@ -398,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/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/evtech.yaml b/static/bidder-info/evtech.yaml
new file mode 100644
index 00000000..4cc75cd3
--- /dev/null
+++ b/static/bidder-info/evtech.yaml
@@ -0,0 +1,16 @@
+endpoint: "http://ads-pbs.direct.e-volution.ai/openrtb/{{.PublisherID}}?host={{.Host}}"
+maintainer:
+  email: "engineering@project-limelight.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - audio
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - audio
+      - native
diff --git a/static/bidder-info/iionads.yaml b/static/bidder-info/iionads.yaml
index 8feb266f..da2f494b 100644
--- a/static/bidder-info/iionads.yaml
+++ b/static/bidder-info/iionads.yaml
@@ -1,4 +1,4 @@
-endpoint: "http://{{.Host}}.iionads.com/openrtb/{{.PublisherID}}"
+endpoint: "http://ads-pbs.iionads.com/openrtb/{{.PublisherID}}?host={{.Host}}"
 maintainer:
   email: "engineering@project-limelight.com"
 capabilities:
diff --git a/static/bidder-info/limelightDigital.yaml b/static/bidder-info/limelightDigital.yaml
index 0db02b0d..22989a63 100644
--- a/static/bidder-info/limelightDigital.yaml
+++ b/static/bidder-info/limelightDigital.yaml
@@ -1,4 +1,4 @@
-endpoint: "http://{{.Host}}.ortb.net/openrtb/{{.PublisherID}}"
+endpoint: "http://ads-pbs.ortb.net/openrtb/{{.PublisherID}}?host={{.Host}}"
 maintainer:
   email: "engineering@project-limelight.com"
 capabilities:
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-params/evtech.json b/static/bidder-params/evtech.json
new file mode 100644
index 00000000..5e78b7de
--- /dev/null
+++ b/static/bidder-params/evtech.json
@@ -0,0 +1,23 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Evolution Technologies Adapter Params",
+  "description": "A schema which validates params accepted by the Evolution Technologies adapter",
+  "type": "object",
+
+  "properties": {
+    "host": {
+      "type": "string",
+      "description": "Ad network's RTB host",
+      "format": "hostname",
+      "pattern": "^.+\\..+$"
+    },
+    "publisherId": {
+      "type": ["integer", "string"],
+      "description": "Publisher ID",
+      "minimum": 1,
+      "pattern": "^[1-9][0-9]*$"
+    }
+  },
+
+  "required": ["host", "publisherId"]
+}
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"]
+}


Test patch:
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/limelightDigital/LimelightDigitaltest/exemplary/audio.json b/adapters/limelightDigital/LimelightDigitaltest/exemplary/audio.json
index 8e40571c..e07b3bfd 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/exemplary/audio.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/exemplary/audio.json
@@ -19,7 +19,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://audio.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=audio.ortb.net",
         "body": {
           "id": "test-request-id-test-audio-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/exemplary/banner.json b/adapters/limelightDigital/LimelightDigitaltest/exemplary/banner.json
index a8fec3f4..cbc72a09 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/exemplary/banner.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/exemplary/banner.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/exemplary/multibid.json b/adapters/limelightDigital/LimelightDigitaltest/exemplary/multibid.json
index 2b888500..cecd5910 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/exemplary/multibid.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/exemplary/multibid.json
@@ -38,7 +38,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
@@ -83,7 +83,7 @@
     },
     {
       "expectedRequest": {
-        "uri": "http://video.ortb.net/openrtb/456789",
+        "uri": "http://test.ortb.net/openrtb/456789?host=video.ortb.net",
         "body": {
           "id": "test-request-id-test-video-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/exemplary/native.json b/adapters/limelightDigital/LimelightDigitaltest/exemplary/native.json
index 36597a46..3b2051f6 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/exemplary/native.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/exemplary/native.json
@@ -19,7 +19,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://native.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=native.ortb.net",
         "body": {
           "id": "test-request-id-test-native-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/exemplary/video.json b/adapters/limelightDigital/LimelightDigitaltest/exemplary/video.json
index f4f3058b..395529d4 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/exemplary/video.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/exemplary/video.json
@@ -21,7 +21,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://video.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=video.ortb.net",
         "body": {
           "id": "test-request-id-test-video-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/204.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/204.json
index 50f2620f..e9decc93 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/204.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/204.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/503.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/503.json
index 9a6fd90b..2ae76c5e 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/503.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/503.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/additional_fields.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/additional_fields.json
index 35e2cac2..9aa50e88 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/additional_fields.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/additional_fields.json
@@ -26,7 +26,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/currency_converter.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/currency_converter.json
index d2b21cc4..0e65ee27 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/currency_converter.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/currency_converter.json
@@ -38,7 +38,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_object_response.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_object_response.json
index 5c502209..7f62a76f 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_object_response.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_object_response.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_reponse.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_reponse.json
index 5d7dd10b..607fa624 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_reponse.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/empty_reponse.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/invalid_hostname.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/invalid_hostname.json
deleted file mode 100644
index 9052ddac..00000000
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/invalid_hostname.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
-  "mockBidRequest": {
-    "id": "test-request-id",
-    "imp": [
-      {
-        "id": "test-banner-id",
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 250
-            }
-          ]
-        },
-        "ext": {
-          "bidder": {
-            "host": "limelightDigital",
-            "publisherId": 123456
-          }
-        }
-      },
-      {
-        "id": "test-banner-id",
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 250
-            }
-          ]
-        },
-        "ext": {
-          "bidder": {
-            "host": ".limelightDigital",
-            "publisherId": 123456
-          }
-        }
-      },
-      {
-        "id": "test-banner-id",
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 250
-            }
-          ]
-        },
-        "ext": {
-          "bidder": {
-            "host": "limelightDigital.",
-            "publisherId": 123456
-          }
-        }
-      }
-    ]
-  },
-  "expectedMakeRequestsErrors": [
-    {
-      "value": "hostname is invalid: limelightDigital",
-      "comparison": "literal"
-    },
-    {
-      "value": "hostname is invalid: .limelightDigital",
-      "comparison": "literal"
-    },
-    {
-      "value": "hostname is invalid: limelightDigital.",
-      "comparison": "literal"
-    }
-  ]
-}
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/publisher_id_string.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/publisher_id_string.json
index 83ac9518..da7716c5 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/publisher_id_string.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/publisher_id_string.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_bid_impid.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_bid_impid.json
index 2baf6a28..157453c1 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_bid_impid.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_bid_impid.json
@@ -24,7 +24,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_imp_media_type.json b/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_imp_media_type.json
index 3814cda3..6eebe900 100644
--- a/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_imp_media_type.json
+++ b/adapters/limelightDigital/LimelightDigitaltest/supplemental/unknown_imp_media_type.json
@@ -16,7 +16,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://banner.ortb.net/openrtb/123456",
+        "uri": "http://test.ortb.net/openrtb/123456?host=banner.ortb.net",
         "body": {
           "id": "test-request-id-test-banner-id",
           "imp": [
diff --git a/adapters/limelightDigital/limelightDigital_test.go b/adapters/limelightDigital/limelightDigital_test.go
index e268b3bc..0c4105b5 100644
--- a/adapters/limelightDigital/limelightDigital_test.go
+++ b/adapters/limelightDigital/limelightDigital_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderLimelightDigital, config.Adapter{
-		Endpoint: "http://{{.Host}}.ortb.net/openrtb/{{.PublisherID}}"},
+		Endpoint: "http://test.ortb.net/openrtb/{{.PublisherID}}?host={{.Host}}"},
 		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
 
 	if buildErr != nil {
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/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..15e015cc
--- /dev/null
+++ b/adservertargeting/respdataprocessor_test.go
@@ -0,0 +1,561 @@
+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"
+	"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")
+		}
+	}
+
+}
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/auction_test.go b/endpoints/openrtb2/auction_test.go
index 43dac63d..2517530d 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")
+			}
 		}
 	}
 }
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/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",