Repository: prebid/prebid-server
Pull request #1949 (2021-08-05 04:34:24)

GumGum: adds slot param
allows publishers to optionally send in a slot param 

update to prebid doc to come


Patch:
diff --git a/adapters/bidder.go b/adapters/bidder.go
index dbe56f8e..e6022777 100644
--- a/adapters/bidder.go
+++ b/adapters/bidder.go
@@ -89,11 +89,13 @@ func NewBidderResponse() *BidderResponse {
 // openrtb_ext.ExtBidPrebid.
 //
 // TypedBid.Bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response.
+// TypedBid.BidMeta will become "response.seatbid[i].bid.ext.prebid.meta" in the final OpenRTB response.
 // TypedBid.BidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response.
 // TypedBid.BidVideo will become "response.seatbid[i].bid.ext.prebid.video" in the final OpenRTB response.
 // TypedBid.DealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns.
 type TypedBid struct {
 	Bid          *openrtb2.Bid
+	BidMeta      *openrtb_ext.ExtBidPrebidMeta
 	BidType      openrtb_ext.BidType
 	BidVideo     *openrtb_ext.ExtBidPrebidVideo
 	DealPriority int
diff --git a/adapters/grid/grid.go b/adapters/grid/grid.go
index bc819fc0..850b21c5 100644
--- a/adapters/grid/grid.go
+++ b/adapters/grid/grid.go
@@ -4,18 +4,33 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"sort"
+	"strings"
 
 	"github.com/mxmCherry/openrtb/v15/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/maputil"
 )
 
 type GridAdapter struct {
 	endpoint string
 }
 
+type GridBidExt struct {
+	Bidder ExtBidder `json:"bidder"`
+}
+
+type ExtBidder struct {
+	Grid ExtBidderGrid `json:"grid"`
+}
+
+type ExtBidderGrid struct {
+	DemandSource string `json:"demandSource"`
+}
+
 type ExtImpDataAdServer struct {
 	Name   string `json:"name"`
 	AdSlot string `json:"adslot"`
@@ -33,6 +48,187 @@ type ExtImp struct {
 	Gpid   string                    `json:"gpid,omitempty"`
 }
 
+type KeywordSegment struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
+type KeywordsPublisherItem struct {
+	Name     string           `json:"name"`
+	Segments []KeywordSegment `json:"segments"`
+}
+
+type KeywordsPublisher map[string][]KeywordsPublisherItem
+
+type Keywords map[string]KeywordsPublisher
+
+// buildConsolidatedKeywordsReqExt builds a new request.ext json incorporating request.site.keywords, request.user.keywords,
+// and request.imp[0].ext.keywords, and request.ext.keywords. Invalid keywords in request.imp[0].ext.keywords are not incorporated.
+// Invalid keywords in request.ext.keywords.site and request.ext.keywords.user are dropped.
+func buildConsolidatedKeywordsReqExt(openRTBUser, openRTBSite string, firstImpExt, requestExt json.RawMessage) (json.RawMessage, error) {
+	// unmarshal ext to object map
+	requestExtMap := parseExtToMap(requestExt)
+	firstImpExtMap := parseExtToMap(firstImpExt)
+	// extract `keywords` field
+	requestExtKeywordsMap := extractKeywordsMap(requestExtMap)
+	firstImpExtKeywordsMap := extractBidderKeywordsMap(firstImpExtMap)
+	// parse + merge keywords
+	keywords := parseKeywordsFromMap(requestExtKeywordsMap)                // request.ext.keywords
+	mergeKeywords(keywords, parseKeywordsFromMap(firstImpExtKeywordsMap))  // request.imp[0].ext.bidder.keywords
+	mergeKeywords(keywords, parseKeywordsFromOpenRTB(openRTBUser, "user")) // request.user.keywords
+	mergeKeywords(keywords, parseKeywordsFromOpenRTB(openRTBSite, "site")) // request.site.keywords
+
+	// overlay site + user keywords
+	if site, exists := keywords["site"]; exists && len(site) > 0 {
+		requestExtKeywordsMap["site"] = site
+	} else {
+		delete(requestExtKeywordsMap, "site")
+	}
+	if user, exists := keywords["user"]; exists && len(user) > 0 {
+		requestExtKeywordsMap["user"] = user
+	} else {
+		delete(requestExtKeywordsMap, "user")
+	}
+	// reconcile keywords with request.ext
+	if len(requestExtKeywordsMap) > 0 {
+		requestExtMap["keywords"] = requestExtKeywordsMap
+	} else {
+		delete(requestExtMap, "keywords")
+	}
+	// marshal final result
+	if len(requestExtMap) > 0 {
+		return json.Marshal(requestExtMap)
+	}
+	return nil, nil
+}
+func parseExtToMap(ext json.RawMessage) map[string]interface{} {
+	var root map[string]interface{}
+	if err := json.Unmarshal(ext, &root); err != nil {
+		return make(map[string]interface{})
+	}
+	return root
+}
+func extractKeywordsMap(ext map[string]interface{}) map[string]interface{} {
+	if keywords, exists := maputil.ReadEmbeddedMap(ext, "keywords"); exists {
+		return keywords
+	}
+	return make(map[string]interface{})
+}
+func extractBidderKeywordsMap(ext map[string]interface{}) map[string]interface{} {
+	if bidder, exists := maputil.ReadEmbeddedMap(ext, "bidder"); exists {
+		return extractKeywordsMap(bidder)
+	}
+	return make(map[string]interface{})
+}
+func parseKeywordsFromMap(extKeywords map[string]interface{}) Keywords {
+	keywords := make(Keywords)
+	for k, v := range extKeywords {
+		// keywords may only be provided in the site and user sections
+		if k != "site" && k != "user" {
+			continue
+		}
+		// the site or user sections must be an object
+		if section, ok := v.(map[string]interface{}); ok {
+			keywords[k] = parseKeywordsFromSection(section)
+		}
+	}
+	return keywords
+}
+func parseKeywordsFromSection(section map[string]interface{}) KeywordsPublisher {
+	keywordsPublishers := make(KeywordsPublisher)
+	for publisherKey, publisherValue := range section {
+		// publisher value must be a slice
+		publisherValueSlice, ok := publisherValue.([]interface{})
+		if !ok {
+			continue
+		}
+		for _, publisherValueItem := range publisherValueSlice {
+			// item must be an object
+			publisherItem, ok := publisherValueItem.(map[string]interface{})
+			if !ok {
+				continue
+			}
+			// publisher item must have a name
+			publisherName, ok := maputil.ReadEmbeddedString(publisherItem, "name")
+			if !ok {
+				continue
+			}
+			var segments []KeywordSegment
+			// extract valid segments
+			if segmentsSlice, exists := maputil.ReadEmbeddedSlice(publisherItem, "segments"); exists {
+				for _, segment := range segmentsSlice {
+					if segmentMap, ok := segment.(map[string]interface{}); ok {
+						name, hasName := maputil.ReadEmbeddedString(segmentMap, "name")
+						value, hasValue := maputil.ReadEmbeddedString(segmentMap, "value")
+						if hasName && hasValue {
+							segments = append(segments, KeywordSegment{Name: name, Value: value})
+						}
+					}
+				}
+			}
+			// ensure consistent ordering for publisher item map
+			publisherItemKeys := make([]string, 0, len(publisherItem))
+			for v := range publisherItem {
+				publisherItemKeys = append(publisherItemKeys, v)
+			}
+			sort.Strings(publisherItemKeys)
+			// compose compatible alternate segment format
+			for _, potentialSegmentName := range publisherItemKeys {
+				potentialSegmentValues := publisherItem[potentialSegmentName]
+				// values must be an array
+				if valuesSlice, ok := potentialSegmentValues.([]interface{}); ok {
+					for _, value := range valuesSlice {
+						if valueAsString, ok := value.(string); ok {
+							segments = append(segments, KeywordSegment{Name: potentialSegmentName, Value: valueAsString})
+						}
+					}
+				}
+			}
+			if len(segments) > 0 {
+				keywordsPublishers[publisherKey] = append(keywordsPublishers[publisherKey], KeywordsPublisherItem{Name: publisherName, Segments: segments})
+			}
+		}
+	}
+	return keywordsPublishers
+}
+func parseKeywordsFromOpenRTB(keywords, section string) Keywords {
+	keywordsSplit := strings.Split(keywords, ",")
+	segments := make([]KeywordSegment, 0, len(keywordsSplit))
+	for _, v := range keywordsSplit {
+		if v != "" {
+			segments = append(segments, KeywordSegment{Name: "keywords", Value: v})
+		}
+	}
+	if len(segments) > 0 {
+		return map[string]KeywordsPublisher{section: map[string][]KeywordsPublisherItem{"ortb2": {{Name: "keywords", Segments: segments}}}}
+	}
+	return make(Keywords)
+}
+func mergeKeywords(a, b Keywords) {
+	for key, values := range b {
+		if _, sectionExists := a[key]; !sectionExists {
+			a[key] = KeywordsPublisher{}
+		}
+		for publisherKey, publisherValues := range values {
+			a[key][publisherKey] = append(publisherValues, a[key][publisherKey]...)
+		}
+	}
+}
+
+func setImpExtKeywords(request *openrtb2.BidRequest) error {
+	userKeywords := ""
+	if request.User != nil {
+		userKeywords = request.User.Keywords
+	}
+	siteKeywords := ""
+	if request.Site != nil {
+		siteKeywords = request.Site.Keywords
+	}
+	var err error
+	request.Ext, err = buildConsolidatedKeywordsReqExt(userKeywords, siteKeywords, request.Imp[0].Ext, request.Ext)
+	return err
+}
+
 func processImp(imp *openrtb2.Imp) error {
 	// get the grid extension
 	var ext adapters.ExtImpBidder
@@ -73,12 +269,10 @@ func setImpExtData(imp openrtb2.Imp) openrtb2.Imp {
 func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	var errors = make([]error, 0)
 
-	// copy the request, because we are going to mutate it
-	requestCopy := *request
 	// this will contain all the valid impressions
 	var validImps []openrtb2.Imp
 	// pre-process the imps
-	for _, imp := range requestCopy.Imp {
+	for _, imp := range request.Imp {
 		if err := processImp(&imp); err == nil {
 			validImps = append(validImps, setImpExtData(imp))
 		} else {
@@ -92,9 +286,15 @@ func (a *GridAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapte
 		errors = append(errors, err)
 		return nil, errors
 	}
-	requestCopy.Imp = validImps
 
-	reqJSON, err := json.Marshal(requestCopy)
+	if err := setImpExtKeywords(request); err != nil {
+		errors = append(errors, err)
+		return nil, errors
+	}
+
+	request.Imp = validImps
+
+	reqJSON, err := json.Marshal(request)
 	if err != nil {
 		errors = append(errors, err)
 		return nil, errors
@@ -138,6 +338,7 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq
 
 	for _, sb := range bidResp.SeatBid {
 		for i := range sb.Bid {
+			bidMeta, err := getBidMeta(sb.Bid[i].Ext)
 			bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
 			if err != nil {
 				return nil, []error{err}
@@ -146,6 +347,7 @@ func (a *GridAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReq
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 				Bid:     &sb.Bid[i],
 				BidType: bidType,
+				BidMeta: bidMeta,
 			})
 		}
 	}
@@ -161,6 +363,21 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters
 	return bidder, nil
 }
 
+func getBidMeta(ext json.RawMessage) (*openrtb_ext.ExtBidPrebidMeta, error) {
+	var bidExt GridBidExt
+
+	if err := json.Unmarshal(ext, &bidExt); err != nil {
+		return nil, err
+	}
+	var bidMeta *openrtb_ext.ExtBidPrebidMeta
+	if bidExt.Bidder.Grid.DemandSource != "" {
+		bidMeta = &openrtb_ext.ExtBidPrebidMeta{
+			NetworkName: bidExt.Bidder.Grid.DemandSource,
+		}
+	}
+	return bidMeta, nil
+}
+
 func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
 	for _, imp := range imps {
 		if imp.ID == impID {
diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go
index 00927b9b..27533127 100644
--- a/adapters/gumgum/gumgum.go
+++ b/adapters/gumgum/gumgum.go
@@ -146,6 +146,16 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) {
 		format := bannerCopy.Format[0]
 		bannerCopy.W = &(format.W)
 		bannerCopy.H = &(format.H)
+
+		if gumgumExt.Slot != 0 {
+			var err error
+			bannerExt := getBiggerFormat(bannerCopy.Format, gumgumExt.Slot)
+			bannerCopy.Ext, err = json.Marshal(&bannerExt)
+			if err != nil {
+				return nil, err
+			}
+		}
+
 		imp.Banner = &bannerCopy
 	}
 
@@ -169,6 +179,30 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) {
 	return &gumgumExt, nil
 }
 
+func getBiggerFormat(formatList []openrtb2.Format, slot float64) openrtb_ext.ExtImpGumGumBanner {
+	maxw := int64(0)
+	maxh := int64(0)
+	greatestVal := int64(0)
+	for _, size := range formatList {
+		var biggerSide int64
+		if size.W > size.H {
+			biggerSide = size.W
+		} else {
+			biggerSide = size.H
+		}
+
+		if biggerSide > greatestVal || (biggerSide == greatestVal && size.W >= maxw && size.H >= maxh) {
+			greatestVal = biggerSide
+			maxh = size.H
+			maxw = size.W
+		}
+	}
+
+	bannerExt := openrtb_ext.ExtImpGumGumBanner{Si: slot, MaxW: float64(maxw), MaxH: float64(maxh)}
+
+	return bannerExt
+}
+
 func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
 	for _, imp := range imps {
 		if imp.ID == impID && imp.Banner != nil {
diff --git a/adapters/smarthub/smarthub.go b/adapters/smarthub/smarthub.go
new file mode 100644
index 00000000..4a374fa2
--- /dev/null
+++ b/adapters/smarthub/smarthub.go
@@ -0,0 +1,183 @@
+package smarthub
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+const (
+	ADAPTER_VER = "1.0.0"
+)
+
+type adapter struct {
+	endpoint template.Template
+}
+
+type bidExt struct {
+	MediaType string `json:"mediaType"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: *template,
+	}
+
+	return bidder, nil
+}
+
+func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSmartHub, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Bidder extension not provided or can't be unmarshalled",
+		}
+	}
+
+	var smarthubExt openrtb_ext.ExtSmartHub
+	if err := json.Unmarshal(bidderExt.Bidder, &smarthubExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error while unmarshaling bidder extension",
+		}
+	}
+
+	return &smarthubExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtSmartHub) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{
+		Host:      params.PartnerName,
+		AccountID: params.Seat,
+		SourceId:  params.Token,
+	}
+	return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+func (a *adapter) MakeRequests(
+	openRTBRequest *openrtb2.BidRequest,
+	reqInfo *adapters.ExtraRequestInfo,
+) (
+	[]*adapters.RequestData,
+	[]error,
+) {
+	var smarthubExt *openrtb_ext.ExtSmartHub
+	smarthubExt, err := a.getImpressionExt(&(openRTBRequest.Imp[0]))
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	url, err := a.buildEndpointURL(smarthubExt)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	reqJSON, err := json.Marshal(openRTBRequest)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	headers.Add("Prebid-Adapter-Ver", ADAPTER_VER)
+
+	return []*adapters.RequestData{{
+		Method:  http.MethodPost,
+		Body:    reqJSON,
+		Uri:     url,
+		Headers: headers,
+	}}, nil
+}
+
+func (a *adapter) MakeBids(
+	openRTBRequest *openrtb2.BidRequest,
+	requestToBidder *adapters.RequestData,
+	bidderRawResponse *adapters.ResponseData,
+) (
+	*adapters.BidderResponse,
+	[]error,
+) {
+	if bidderRawResponse.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if bidderRawResponse.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)),
+		}}
+	}
+
+	if bidderRawResponse.StatusCode == http.StatusServiceUnavailable {
+		return nil, []error{&errortypes.BadInput{
+			Message: "Bidder unavailable. Please contact the bidder support.",
+		}}
+	}
+
+	if bidderRawResponse.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)),
+		}}
+	}
+
+	responseBody := bidderRawResponse.Body
+	var bidResp openrtb2.BidResponse
+	if err := json.Unmarshal(responseBody, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	if len(bidResp.SeatBid) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Array SeatBid cannot be empty",
+		}}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+	bids := bidResp.SeatBid[0].Bid
+
+	if len(bids) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Array SeatBid[0].Bid cannot be empty",
+		}}
+	}
+
+	bid := bids[0]
+
+	var bidExt bidExt
+	var bidType openrtb_ext.BidType
+
+	if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Field BidExt is required",
+		}}
+	}
+
+	bidType, err := getBidType(bidExt)
+
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+		Bid:     &bid,
+		BidType: bidType,
+	})
+	return bidResponse, nil
+}
+
+func getBidType(ext bidExt) (openrtb_ext.BidType, error) {
+	return openrtb_ext.ParseBidType(ext.MediaType)
+}
diff --git a/adapters/smarthub/usersync.go b/adapters/smarthub/usersync.go
new file mode 100644
index 00000000..3cd4de2a
--- /dev/null
+++ b/adapters/smarthub/usersync.go
@@ -0,0 +1,12 @@
+package smarthub
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+func NewSmartHubSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("smarthub", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go
index 7d7a8f22..eaaa489d 100644
--- a/adapters/yieldmo/yieldmo.go
+++ b/adapters/yieldmo/yieldmo.go
@@ -16,8 +16,18 @@ type YieldmoAdapter struct {
 	endpoint string
 }
 
+type ExtImpBidderYieldmo struct {
+	adapters.ExtImpBidder
+	Data *ExtData `json:"data,omitempty"`
+}
+
+type ExtData struct {
+	PbAdslot string `json:"pbadslot"`
+}
+
 type Ext struct {
 	PlacementId string `json:"placement_id"`
+	Gpid        string `json:"gpid,omitempty"`
 }
 
 func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
@@ -62,7 +72,7 @@ func (a *YieldmoAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.Re
 func preprocess(request *openrtb2.BidRequest) error {
 	for i := 0; i < len(request.Imp); i++ {
 		var imp = request.Imp[i]
-		var bidderExt adapters.ExtImpBidder
+		var bidderExt ExtImpBidderYieldmo
 
 		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
 			return &errortypes.BadInput{
@@ -81,6 +91,12 @@ func preprocess(request *openrtb2.BidRequest) error {
 		var impExt Ext
 		impExt.PlacementId = yieldmoExt.PlacementId
 
+		if bidderExt.Data != nil {
+			if bidderExt.Data.PbAdslot != "" {
+				impExt.Gpid = bidderExt.Data.PbAdslot
+			}
+		}
+
 		impExtJSON, err := json.Marshal(impExt)
 		if err != nil {
 			return &errortypes.BadInput{
diff --git a/config/config.go b/config/config.go
index b1d0553f..22cbac05 100644
--- a/config/config.go
+++ b/config/config.go
@@ -674,6 +674,7 @@ func (cfg *Configuration) setDerivedDefaults() {
 	// openrtb_ext.BidderRubicon doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSharethrough, "https://match.sharethrough.com/FGMrCMMc/v1?redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsharethrough%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartAdserver, "https://ssbsync-global.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bssb_sync_pid%5D")
+	// openrtb_ext.BidderSmartHub doesn't have a good default.
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartyAds, "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D")
 	setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmileWanted, "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
@@ -910,7 +911,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid")
 	v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs")
 	v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid")
-	v.SetDefault("adapters.interactiveoffers.endpoint", "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04")
+	v.SetDefault("adapters.interactiveoffers.endpoint", "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04")
 	v.SetDefault("adapters.ix.disabled", true)
 	v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost")
 	v.SetDefault("adapters.kayzen.endpoint", "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}")
@@ -950,6 +951,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.silvermob.endpoint", "http://{{.Host}}.silvermob.com/marketplace/api/dsp/bid/{{.ZoneID}}")
 	v.SetDefault("adapters.smaato.endpoint", "https://prebid.ad.smaato.net/oapi/prebid")
 	v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com")
+	v.SetDefault("adapters.smarthub.endpoint", "http://{{.Host}}-prebid.smart-hub.io/?seat={{.AccountID}}&token={{.SourceId}}")
 	v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.smartyads.endpoint", "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}")
 	v.SetDefault("adapters.smilewanted.endpoint", "http://prebid-server.smilewanted.com")
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index a6b3479a..bcdf133c 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -135,7 +135,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		deps.analytics.LogAuctionObject(&ao)
 	}()
 
-	req, errL := deps.parseRequest(r)
+	req, impExtInfoMap, errL := deps.parseRequest(r)
 
 	if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
 		return
@@ -192,6 +192,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		LegacyLabels:               labels,
 		Warnings:                   warnings,
 		GlobalPrivacyControlHeader: secGPC,
+		ImpExtInfoMap:              impExtInfoMap,
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
@@ -234,7 +235,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 // possible, it will return errors with messages that suggest improvements.
 //
 // If the errors list has at least one element, then no guarantees are made about the returned request.
-func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, errs []error) {
+func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, errs []error) {
 	req = &openrtb_ext.RequestWrapper{}
 	req.BidRequest = &openrtb2.BidRequest{}
 	errs = nil
@@ -262,7 +263,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_
 	defer cancel()
 
 	// Fetch the Stored Request data and merge it into the HTTP request.
-	if requestJson, errs = deps.processStoredRequests(ctx, requestJson); len(errs) > 0 {
+	if requestJson, impExtInfoMap, errs = deps.processStoredRequests(ctx, requestJson); len(errs) > 0 {
 		return
 	}
 
@@ -1316,15 +1317,15 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) {
 	return false, ""
 }
 
-func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte) ([]byte, []error) {
+func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte) ([]byte, map[string]exchange.ImpExtInfo, []error) {
 	// Parse the Stored Request IDs from the BidRequest and Imps.
 	storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson)
 	if err != nil {
-		return nil, []error{err}
+		return nil, nil, []error{err}
 	}
 	imps, impIds, idIndices, errs := parseImpInfo(requestJson)
 	if len(errs) > 0 {
-		return nil, errs
+		return nil, nil, errs
 	}
 
 	// Fetch the Stored Request data
@@ -1334,7 +1335,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson
 	}
 	storedRequests, storedImps, errs := deps.storedReqFetcher.FetchRequests(ctx, storedReqIds, impIds)
 	if len(errs) != 0 {
-		return nil, errs
+		return nil, nil, errs
 	}
 
 	// Apply the Stored BidRequest, if it exists
@@ -1352,7 +1353,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson
 					err = fmt.Errorf("ext.prebid.storedrequest.id refers to Stored Request %s which contains Invalid JSON: %s", storedBidRequestId, Err)
 				}
 			}
-			return nil, []error{err}
+			return nil, nil, []error{err}
 		}
 	}
 
@@ -1369,7 +1370,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson
 					err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", Err)
 				}
 			}
-			return nil, []error{err}
+			return nil, nil, []error{err}
 		}
 		resolvedRequest = aliasedRequest
 	}
@@ -1377,8 +1378,10 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson
 	// Apply any Stored Imps, if they exist. Since the JSON Merge Patch overrides arrays,
 	// and Prebid Server defers to the HTTP Request to resolve conflicts, it's safe to
 	// assume that the request.imp data did not change when applying the Stored BidRequest.
+	impExtInfoMap := make(map[string]exchange.ImpExtInfo, len(impIds))
 	for i := 0; i < len(impIds); i++ {
 		resolvedImp, err := jsonpatch.MergePatch(storedImps[impIds[i]], imps[idIndices[i]])
+
 		if err != nil {
 			hasErr, Err := getJsonSyntaxError(imps[idIndices[i]])
 			if hasErr {
@@ -1389,22 +1392,38 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson
 					err = fmt.Errorf("imp.ext.prebid.storedrequest.id %s: Stored Imp has Invalid JSON: %s", impIds[i], Err)
 				}
 			}
-			return nil, []error{err}
+			return nil, nil, []error{err}
 		}
 		imps[idIndices[i]] = resolvedImp
+
+		impId, err := jsonparser.GetString(resolvedImp, "id")
+		if err != nil {
+			return nil, nil, []error{err}
+		}
+		// This is substantially faster for reading values from a json blob than the Go json package,
+		// but keep in mind that each jsonparser.GetXXX call re-parses the entire json.
+		// Based on performance measurements, the tipping point of efficiency is around 4 calls.
+		// At that point, please consider switching to EachKey to use a single pass.
+		includeVideoAttributes, err := jsonparser.GetBoolean(resolvedImp, "ext", "prebid", "options", "echovideoattrs")
+		if err != nil && err != jsonparser.KeyPathNotFoundError {
+			return nil, nil, []error{err}
+		}
+		if storedImps[impIds[i]] != nil {
+			impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: includeVideoAttributes, StoredImp: storedImps[impIds[i]]}
+		}
 	}
 	if len(impIds) > 0 {
 		newImpJson, err := json.Marshal(imps)
 		if err != nil {
-			return nil, []error{err}
+			return nil, nil, []error{err}
 		}
 		resolvedRequest, err = jsonparser.Set(resolvedRequest, newImpJson, "imp")
 		if err != nil {
-			return nil, []error{err}
+			return nil, nil, []error{err}
 		}
 	}
 
-	return resolvedRequest, nil
+	return resolvedRequest, impExtInfoMap, nil
 }
 
 // parseImpInfo parses the request JSON and returns several things about the Imps
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index e1dbc350..7a61a914 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -97,6 +97,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/silvermob"
 	"github.com/prebid/prebid-server/adapters/smaato"
 	"github.com/prebid/prebid-server/adapters/smartadserver"
+	"github.com/prebid/prebid-server/adapters/smarthub"
 	"github.com/prebid/prebid-server/adapters/smartrtb"
 	"github.com/prebid/prebid-server/adapters/smartyads"
 	"github.com/prebid/prebid-server/adapters/smilewanted"
@@ -224,6 +225,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderSilverMob:         silvermob.Builder,
 		openrtb_ext.BidderSmaato:            smaato.Builder,
 		openrtb_ext.BidderSmartAdserver:     smartadserver.Builder,
+		openrtb_ext.BidderSmartHub:          smarthub.Builder,
 		openrtb_ext.BidderSmartRTB:          smartrtb.Builder,
 		openrtb_ext.BidderSmartyAds:         smartyads.Builder,
 		openrtb_ext.BidderSmileWanted:       smilewanted.Builder,
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 83466c7d..895cff93 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -55,6 +55,7 @@ type adaptedBidder interface {
 // pbsOrtbBid is a Bid returned by an adaptedBidder.
 //
 // pbsOrtbBid.bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response.
+// pbsOrtbBid.bidMeta will become "response.seatbid[i].bid.ext.prebid.meta" in the final OpenRTB response.
 // pbsOrtbBid.bidType will become "response.seatbid[i].bid.ext.prebid.type" in the final OpenRTB response.
 // pbsOrtbBid.bidTargets does not need to be filled out by the Bidder. It will be set later by the exchange.
 // pbsOrtbBid.bidVideo is optional but should be filled out by the Bidder if bidType is video.
@@ -64,6 +65,7 @@ type adaptedBidder interface {
 // pbsOrtbBid.generatedBidID is unique bid id generated by prebid server if generate bid id option is enabled in config
 type pbsOrtbBid struct {
 	bid               *openrtb2.Bid
+	bidMeta           *openrtb_ext.ExtBidPrebidMeta
 	bidType           openrtb_ext.BidType
 	bidTargets        map[string]string
 	bidVideo          *openrtb_ext.ExtBidPrebidVideo
@@ -251,6 +253,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B
 						}
 						seatBid.bids = append(seatBid.bids, &pbsOrtbBid{
 							bid:          bidResponse.Bids[i].Bid,
+							bidMeta:      bidResponse.Bids[i].BidMeta,
 							bidType:      bidResponse.Bids[i].BidType,
 							bidVideo:     bidResponse.Bids[i].BidVideo,
 							dealPriority: bidResponse.Bids[i].DealPriority,
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 6aaafcd1..1cedb5d9 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -14,11 +14,10 @@ import (
 	"strings"
 	"time"
 
-	"github.com/mxmCherry/openrtb/v15/openrtb2"
-	"github.com/prebid/prebid-server/stored_requests"
-
+	"github.com/buger/jsonparser"
 	"github.com/gofrs/uuid"
 	"github.com/golang/glog"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
@@ -27,6 +26,7 @@ import (
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/prebid_cache_client"
+	"github.com/prebid/prebid-server/stored_requests"
 )
 
 type ContextKey string
@@ -135,6 +135,11 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid
 	}
 }
 
+type ImpExtInfo struct {
+	EchoVideoAttrs bool
+	StoredImp      []byte
+}
+
 // AuctionRequest holds the bid request for the auction
 // and all other information needed to process that request
 type AuctionRequest struct {
@@ -145,6 +150,7 @@ type AuctionRequest struct {
 	StartTime                  time.Time
 	Warnings                   []error
 	GlobalPrivacyControlHeader string
+	ImpExtInfoMap              map[string]ImpExtInfo
 
 	// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
 	// in HoldAuction until we get to factoring it away. Do not use for anything new.
@@ -303,7 +309,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	}
 
 	// Build the response
-	return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs)
+	return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, errs)
 }
 
 func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal {
@@ -452,6 +458,7 @@ func (e *exchange) getAllBids(
 			reqInfo := adapters.NewExtraRequestInfo(conversions)
 			reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
 			reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
+
 			bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed)
 
 			// Add in time reporting
@@ -589,7 +596,7 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage {
 }
 
 // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester
-func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, errList []error) (*openrtb2.BidResponse, error) {
+func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, errList []error) (*openrtb2.BidResponse, error) {
 	bidResponse := new(openrtb2.BidResponse)
 	var err error
 
@@ -605,7 +612,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 	for _, a := range liveAdapters {
 		//while processing every single bib, do we need to handle categories here?
 		if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 {
-			sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc, returnCreative)
+			sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc, returnCreative, impExtInfoMap)
 			seatBids = append(seatBids, *sb)
 			bidResponse.Cur = adapterBids[a].currency
 		}
@@ -899,14 +906,14 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb
 
 // Return an openrtb seatBid for a bidder
 // BuildBidResponse is responsible for ensuring nil bid seatbids are not included
-func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb2.SeatBid {
+func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) *openrtb2.SeatBid {
 	seatBid := &openrtb2.SeatBid{
 		Seat:  adapter.String(),
 		Group: 0, // Prebid cannot support roadblocking
 	}
 
 	var errList []error
-	seatBid.Bid, errList = e.makeBid(adapterBid.bids, auc, returnCreative)
+	seatBid.Bid, errList = e.makeBid(adapterBid.bids, auc, returnCreative, impExtInfoMap)
 	if len(errList) > 0 {
 		adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, errsToBidderErrors(errList)...)
 	}
@@ -914,7 +921,7 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B
 	return seatBid
 }
 
-func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb2.Bid, []error) {
+func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo) ([]openrtb2.Bid, []error) {
 	result := make([]openrtb2.Bid, 0, len(bids))
 	errs := make([]error, 0, 1)
 
@@ -925,6 +932,7 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool
 			Events:            bid.bidEvents,
 			Targeting:         bid.bidTargets,
 			Type:              bid.bidType,
+			Meta:              bid.bidMeta,
 			Video:             bid.bidVideo,
 			BidId:             bid.generatedBidID,
 		}
@@ -935,7 +943,7 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool
 			}
 		}
 
-		if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid); err != nil {
+		if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid, impExtInfoMap, bid.bid.ImpID); err != nil {
 			errs = append(errs, err)
 		} else {
 			result = append(result, *bid.bid)
@@ -949,19 +957,34 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool
 	return result, errs
 }
 
-func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid) (json.RawMessage, error) {
-	// no existing bid.ext. generate a bid.ext with just our prebid section populated.
-	if len(ext) == 0 {
-		bidExt := &openrtb_ext.ExtBid{Prebid: prebid}
-		return json.Marshal(bidExt)
-	}
-
-	// update existing bid.ext with our prebid section. if bid.ext.prebid already exists, it will be overwritten.
+func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid, impExtInfoMap map[string]ImpExtInfo, impId string) (json.RawMessage, error) {
+	// update existing bid.ext with prebid section
+	// if bid.ext.prebid already exists, it will be overwritten.
+	// if echoVideoAttrs set to true stored video attributes will be added to bid.ext.storedrequestattributes
 	var extMap map[string]interface{}
-	if err := json.Unmarshal(ext, &extMap); err != nil {
-		return nil, err
+
+	if len(ext) != 0 {
+		if err := json.Unmarshal(ext, &extMap); err != nil {
+			return nil, err
+		}
+	} else {
+		extMap = make(map[string]interface{})
 	}
+
 	extMap[openrtb_ext.PrebidExtKey] = prebid
+
+	if impExtInfo, ok := impExtInfoMap[impId]; ok && impExtInfo.EchoVideoAttrs {
+
+		videoData, _, _, err := jsonparser.Get(impExtInfo.StoredImp, "video")
+		if err != nil && err != jsonparser.KeyPathNotFoundError {
+			return nil, err
+		}
+		//handler for case where EchoVideoAttrs is true, but video data is not found
+		if len(videoData) > 0 {
+			extMap[openrtb_ext.StoredRequestAttributes] = json.RawMessage(videoData)
+		}
+	}
+
 	return json.Marshal(extMap)
 }
 
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index 535ac76b..ce6e25fd 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -157,3 +157,7 @@ func min(x, y int) int {
 	}
 	return y
 }
+
+const (
+	StoredRequestAttributes = "storedrequestattributes"
+)
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 2a08fd19..8e74fcc6 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -169,6 +169,7 @@ const (
 	BidderSilverMob         BidderName = "silvermob"
 	BidderSmaato            BidderName = "smaato"
 	BidderSmartAdserver     BidderName = "smartadserver"
+	BidderSmartHub          BidderName = "smarthub"
 	BidderSmartRTB          BidderName = "smartrtb"
 	BidderSmartyAds         BidderName = "smartyads"
 	BidderSmileWanted       BidderName = "smilewanted"
@@ -295,6 +296,7 @@ func CoreBidderNames() []BidderName {
 		BidderSilverMob,
 		BidderSmaato,
 		BidderSmartAdserver,
+		BidderSmartHub,
 		BidderSmartRTB,
 		BidderSmartyAds,
 		BidderSmileWanted,
diff --git a/openrtb_ext/imp_grid.go b/openrtb_ext/imp_grid.go
index d38e610d..c4530426 100644
--- a/openrtb_ext/imp_grid.go
+++ b/openrtb_ext/imp_grid.go
@@ -1,6 +1,9 @@
 package openrtb_ext
 
+import "encoding/json"
+
 // ExtImpGrid defines the contract for bidrequest.imp[i].ext.grid
 type ExtImpGrid struct {
-	Uid int `json:"uid"`
+	Uid      int             `json:"uid"`
+	Keywords json.RawMessage `json:"keywords,omitempty"`
 }
diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go
index 56b000a6..8e0d7dd8 100644
--- a/openrtb_ext/imp_gumgum.go
+++ b/openrtb_ext/imp_gumgum.go
@@ -6,9 +6,17 @@ type ExtImpGumGum struct {
 	Zone   string  `json:"zone,omitempty"`
 	PubID  float64 `json:"pubId,omitempty"`
 	IrisID string  `json:"irisid,omitempty"`
+	Slot   float64 `json:"slot,omitempty"`
 }
 
 // ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.video
 type ExtImpGumGumVideo struct {
 	IrisID string `json:"irisid,omitempty"`
 }
+
+// ExtImpGumGumBanner defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.banner
+type ExtImpGumGumBanner struct {
+	Si   float64 `json:"si,omitempty"`
+	MaxW float64 `json:"maxw,omitempty"`
+	MaxH float64 `json:"maxh,omitempty"`
+}
diff --git a/openrtb_ext/imp_smarthub.go b/openrtb_ext/imp_smarthub.go
new file mode 100644
index 00000000..37234baf
--- /dev/null
+++ b/openrtb_ext/imp_smarthub.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ExtSmartHub struct {
+	PartnerName string `json:"partnerName,omitempty"`
+	Seat        string `json:"seat,omitempty"`
+	Token       string `json:"token,omitempty"`
+}
diff --git a/static/bidder-info/smarthub.yaml b/static/bidder-info/smarthub.yaml
new file mode 100644
index 00000000..bfee9490
--- /dev/null
+++ b/static/bidder-info/smarthub.yaml
@@ -0,0 +1,13 @@
+maintainer:
+  email: "support@smart-hub.io"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
diff --git a/static/bidder-params/grid.json b/static/bidder-params/grid.json
index 67f9b12f..42c88584 100644
--- a/static/bidder-params/grid.json
+++ b/static/bidder-params/grid.json
@@ -7,6 +7,18 @@
     "uid": {
       "type": "integer",
       "description": "An ID which identifies this placement of the impression"
+    },
+    "keywords": {
+      "type": "object",
+      "description": "Keywords",
+      "properties": {
+        "site": {
+          "type": "object"
+        },
+        "user": {
+          "type": "object"
+        }
+      }
     }
   },
   "required": []
diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json
index 8c8677d9..95f05e7d 100644
--- a/static/bidder-params/gumgum.json
+++ b/static/bidder-params/gumgum.json
@@ -16,6 +16,10 @@
     "irisid": {
       "type": "string",
       "description": "A hashed IRIS.TV Content ID"
+    },
+    "slot": {
+      "type": "integer",
+      "description": "A slot id used to identify a slot placement mapped to a GumGum zone or publisher"
     }
   },
   "anyOf": [
diff --git a/static/bidder-params/smarthub.json b/static/bidder-params/smarthub.json
new file mode 100644
index 00000000..dd9888fa
--- /dev/null
+++ b/static/bidder-params/smarthub.json
@@ -0,0 +1,28 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "SmartHub Adapter Params",
+  "description": "A schema which validates params accepted by the SmartHub adapter",
+  "type": "object",
+  "properties": {
+    "partnerName": {
+      "type": "string",
+      "description": "SmartHub unique partner name",
+      "minLength": 1
+    },
+    "seat": {
+      "type": "string",
+      "description": "Seat",
+      "minLength": 1
+    },
+    "token": {
+      "type": "string",
+      "description": "Token",
+      "minLength": 1
+    }
+  },
+  "required": [
+    "partnerName",
+    "seat",
+    "token"
+  ]
+}
\ No newline at end of file
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 8275869f..87f8cf25 100644
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -76,6 +76,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/sa_lunamedia"
 	"github.com/prebid/prebid-server/adapters/sharethrough"
 	"github.com/prebid/prebid-server/adapters/smartadserver"
+	"github.com/prebid/prebid-server/adapters/smarthub"
 	"github.com/prebid/prebid-server/adapters/smartrtb"
 	"github.com/prebid/prebid-server/adapters/smartyads"
 	"github.com/prebid/prebid-server/adapters/smilewanted"
@@ -183,6 +184,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSonobi, sonobi.NewSonobiSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSovrn, sovrn.NewSovrnSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartHub, smarthub.NewSmartHubSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderSmileWanted, smilewanted.NewSmileWantedSyncer)
diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go
index 963477de..4fd56745 100644
--- a/util/jsonutil/jsonutil.go
+++ b/util/jsonutil/jsonutil.go
@@ -7,8 +7,10 @@ import (
 )
 
 var comma = []byte(",")[0]
+var colon = []byte(":")[0]
 
-func DropElement(extension []byte, elementName string) ([]byte, error) {
+func findElementIndexes(extension []byte, elementName string) (bool, int64, int64, error) {
+	found := false
 	buf := bytes.NewBuffer(extension)
 	dec := json.NewDecoder(buf)
 	var startIndex int64
@@ -20,14 +22,15 @@ func DropElement(extension []byte, elementName string) ([]byte, error) {
 			break
 		}
 		if err != nil {
-			return nil, err
+			return false, -1, -1, err
 		}
 
 		if token == elementName {
 			err := dec.Decode(&i)
 			if err != nil {
-				return nil, err
+				return false, -1, -1, err
 			}
+			found = true
 			endIndex := dec.InputOffset()
 
 			if dec.More() {
@@ -45,13 +48,43 @@ func DropElement(extension []byte, elementName string) ([]byte, error) {
 					endIndex++
 				}
 			}
-
-			extension = append(extension[:startIndex], extension[endIndex:]...)
-			break
+			return found, startIndex, endIndex, nil
 		} else {
 			startIndex = dec.InputOffset()
 		}
 
 	}
-	return extension, nil
+
+	return false, -1, -1, nil
+}
+
+func DropElement(extension []byte, elementName string) ([]byte, error) {
+	found, startIndex, endIndex, err := findElementIndexes(extension, elementName)
+	if found {
+		extension = append(extension[:startIndex], extension[endIndex:]...)
+	}
+	return extension, err
+}
+
+func FindElement(extension []byte, elementName string) (bool, []byte, error) {
+
+	found, startIndex, endIndex, err := findElementIndexes(extension, elementName)
+
+	if found && err == nil {
+		element := extension[startIndex:endIndex]
+		index := 0
+		for {
+			if index < len(element) && element[index] != colon {
+				index++
+			} else {
+				index++
+				break
+			}
+		}
+		element = element[index:]
+		return found, element, err
+	}
+
+	return found, nil, err
+
 }
diff --git a/util/maputil/maputil.go b/util/maputil/maputil.go
index 0d1d7dbb..595e9321 100644
--- a/util/maputil/maputil.go
+++ b/util/maputil/maputil.go
@@ -19,3 +19,12 @@ func ReadEmbeddedSlice(m map[string]interface{}, k string) ([]interface{}, bool)
 
 	return nil, false
 }
+
+// ReadEmbeddedString reads element k from the map m as a string
+func ReadEmbeddedString(m map[string]interface{}, k string) (string, bool) {
+	if v, ok := m[k]; ok {
+		vCasted, ok := v.(string)
+		return vCasted, ok
+	}
+	return "", false
+}


Test patch:
diff --git a/adapters/grid/gridtest/exemplary/simple-banner.json b/adapters/grid/gridtest/exemplary/simple-banner.json
index 1a5ea014..1161e482 100644
--- a/adapters/grid/gridtest/exemplary/simple-banner.json
+++ b/adapters/grid/gridtest/exemplary/simple-banner.json
@@ -4,6 +4,11 @@
         "site": {
             "page": "https://good.site/url"
         },
+        "ext": {
+            "keywords": {
+                "anyName": "anyVal"
+            }
+        },
         "imp": [{
             "id": "test-imp-id",
             "banner": {
@@ -28,6 +33,11 @@
                 "site": {
                     "page": "https://good.site/url"
                 },
+                "ext": {
+                    "keywords": {
+                        "anyName": "anyVal"
+                    }
+                },
                 "imp": [{
                     "id": "test-imp-id",
                     "banner": {
@@ -59,7 +69,12 @@
                         "cid": "987",
                         "crid": "12345678",
                         "h": 250,
-                        "w": 300
+                        "w": 300,
+                        "ext": {
+                            "bidder": {
+                                "grid": {"demandSource":"trustx"}
+                            }
+                        }
                     }]
                 }],
                 "cur": "USD"
@@ -79,7 +94,12 @@
                 "cid": "987",
                 "crid": "12345678",
                 "w": 300,
-                "h": 250
+                "h": 250,
+                "ext": {
+                    "bidder": {
+                        "grid": {"demandSource":"trustx"}
+                    }
+                }
             },
             "type": "banner"
         }]
diff --git a/adapters/grid/gridtest/exemplary/with-ext-keywords.json b/adapters/grid/gridtest/exemplary/with-ext-keywords.json
new file mode 100644
index 00000000..e0d0563c
--- /dev/null
+++ b/adapters/grid/gridtest/exemplary/with-ext-keywords.json
@@ -0,0 +1,216 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url"
+        },
+        "ext": {
+            "keywords": {
+                "stringKey": "stringVal",
+                "wrongKeys1": {
+                    "someKey1": "someVal1",
+                    "someKey2": "someVal2",
+                    "someKey3": ["someVal31", "someVal32"],
+                    "someKey4": { "key1": "val1", "key2": "val2" }
+                },
+                "anotherKeys": [
+                    {"someKey1":  "someVal1"},
+                    {"someKey2":  "someVal2"}
+                ],
+                "site": {
+                    "stringSiteKey": "stringSiteVal",
+                    "wrongSiteKeys1": {
+                        "someKey1": "someVal1",
+                        "someKey2": "someVal2",
+                        "someKey3": ["someVal31", "someVal32"],
+                        "someKey4": { "key1": "val1", "key2": "val2" }
+                    },
+                    "anotherSiteKeys": [
+                        {"someKey1":  "someVal1"},
+                        {"someKey2":  "someVal2"},
+                        "someStrKey",
+                        ["someVal31", "someVal32"],
+                        {
+                            "name":  "someName3",
+                            "keyName": ["keyVal", { "name": "wrongKey" }],
+                            "wrongKeyName": "stKeyVal",
+                            "wrongKeyName2": { "name": "someName", "value": "stKeyVal" }
+                        }
+                    ],
+                    "pub": [
+                        "k1",
+                        "k2"
+                    ],
+                    "somePublisher": [
+                        {
+                            "name": "someName2",
+                            "topic": ["anyKey"]
+                        }
+                    ],
+                    "formatedSitePublisher": [
+                        {
+                            "name": "formatedPub2",
+                            "segments": [
+                                { "name": "segName1", "value": "segVal1" },
+                                { "name": "segName2", "value": "segVal2" }
+                            ]
+                        },
+                        {
+                            "name": "notFormatedPub",
+                            "topic2": ["notFormatedKw"]
+                        }
+                    ]
+                },
+                "user": {
+                    "formatedUserPublisher": [
+                        {
+                            "name": "formatedPub2",
+                            "segments": [
+                                { "name": "segName1", "value": "segVal1" },
+                                { "name": "segName2", "value": "segVal2" }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "uid": 1
+                }
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "body": {
+                "ext": {
+                    "keywords": {
+                        "site": {
+                            "anotherSiteKeys": [
+                                {
+                                    "name":  "someName3",
+                                    "segments": [
+                                        { "name": "keyName", "value": "keyVal" }
+                                    ]
+                                }
+                            ],
+                            "somePublisher": [
+                                {
+                                    "name": "someName2",
+                                    "segments": [
+                                        { "name": "topic", "value": "anyKey" }
+                                    ]
+                                }
+                            ],
+                            "formatedSitePublisher": [
+                                {
+                                    "name": "formatedPub2",
+                                    "segments": [
+                                        { "name": "segName1", "value": "segVal1" },
+                                        { "name": "segName2", "value": "segVal2" }
+                                    ]
+                                },
+                                {
+                                    "name": "notFormatedPub",
+                                    "segments": [
+                                        { "name": "topic2", "value": "notFormatedKw" }
+                                    ]
+                                }
+                            ]
+                        },
+                        "user": {
+                            "formatedUserPublisher": [
+                                {
+                                    "name": "formatedPub2",
+                                    "segments": [
+                                        { "name": "segName1", "value": "segVal1" },
+                                        { "name": "segName2", "value": "segVal2" }
+                                    ]
+                                }
+                            ]
+                        },
+                        "wrongKeys1": {
+                            "someKey1": "someVal1",
+                            "someKey2": "someVal2",
+                            "someKey3": ["someVal31", "someVal32"],
+                            "someKey4": { "key1": "val1", "key2": "val2" }
+                        },
+                        "stringKey": "stringVal",
+                        "anotherKeys": [
+                            {"someKey1":  "someVal1"},
+                            {"someKey2":  "someVal2"}
+                        ]
+                    }
+                },
+                "id": "test-request-id",
+                "site": {
+                    "page": "https://good.site/url"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {
+                            "uid": 1
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "grid",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/grid/gridtest/exemplary/with-keywords.json b/adapters/grid/gridtest/exemplary/with-keywords.json
new file mode 100644
index 00000000..a8875220
--- /dev/null
+++ b/adapters/grid/gridtest/exemplary/with-keywords.json
@@ -0,0 +1,122 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url"
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "uid": 1,
+                    "keywords": {
+                        "site": {
+                            "somePublisher": [
+                                {
+                                    "name": "someName",
+                                    "topic": ["stress", "fear"]
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "body": {
+                "ext": {
+                    "keywords": {
+                        "site": {
+                            "somePublisher": [
+                                {
+                                    "name": "someName",
+                                    "segments": [
+                                        { "name":  "topic", "value": "stress" },
+                                        { "name":  "topic", "value": "fear" }
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                },
+                "id": "test-request-id",
+                "site": {
+                    "page": "https://good.site/url"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {
+                            "uid": 1,
+                            "keywords": {
+                                "site": {
+                                    "somePublisher": [
+                                        {
+                                            "name": "someName",
+                                            "topic": ["stress", "fear"]
+                                        }
+                                    ]
+                                }
+                            }
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "grid",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/grid/gridtest/exemplary/with-mixed-keywords.json b/adapters/grid/gridtest/exemplary/with-mixed-keywords.json
new file mode 100644
index 00000000..49831991
--- /dev/null
+++ b/adapters/grid/gridtest/exemplary/with-mixed-keywords.json
@@ -0,0 +1,308 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url",
+            "keywords": "siteKey1,siteKey2"
+        },
+        "ext": {
+            "keywords": {
+                "stringKey": "stringVal",
+                "wrongKeys1": {
+                    "someKey1": "someVal1",
+                    "someKey2": "someVal2",
+                    "someKey3": ["someVal31", "someVal32"],
+                    "someKey4": { "key1": "val1", "key2": "val2" }
+                },
+                "anotherKeys": [
+                    {"someKey1":  "someVal1"},
+                    {"someKey2":  "someVal2"}
+                ],
+                "site": {
+                    "stringSiteKey": "stringSiteVal",
+                    "wrongSiteKeys1": {
+                        "someKey1": "someVal1",
+                        "someKey2": "someVal2",
+                        "someKey3": ["someVal31", "someVal32"],
+                        "someKey4": { "key1": "val1", "key2": "val2" }
+                    },
+                    "anotherSiteKeys": [
+                        {"someKey1":  "someVal1"},
+                        {"someKey2":  "someVal2"},
+                        "someStrKey",
+                        ["someVal31", "someVal32"],
+                        {
+                            "name":  "someName3",
+                            "keyName": ["keyVal", { "name": "wrongKey" }],
+                            "wrongKeyName": "stKeyVal",
+                            "wrongKeyName2": { "name": "someName", "value": "stKeyVal" }
+                        }
+                    ],
+                    "pub": [
+                        "k1",
+                        "k2"
+                    ],
+                    "somePublisher": [
+                        {
+                            "name": "someName2",
+                            "topic": ["anyKey"]
+                        }
+                    ],
+                    "formatedSitePublisher": [
+                        {
+                            "name": "formatedPub2",
+                            "segments": [
+                                { "name": "segName1", "value": "segVal1" },
+                                { "name": "segName2", "value": "segVal2" }
+                            ]
+                        },
+                        {
+                            "name": "notFormatedPub",
+                            "topic2": ["notFormatedKw"]
+                        }
+                    ]
+                },
+                "user": {
+                    "formatedUserPublisher": [
+                        {
+                            "name": "formatedPub2",
+                            "segments": [
+                                { "name": "segName1", "value": "segVal1" },
+                                { "name": "segName2", "value": "segVal2" }
+                            ]
+                        }
+                    ]
+                }
+            }
+        },
+        "user": {
+            "keywords": "userKey1"
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "uid": 1,
+                    "keywords": {
+                        "site": {
+                            "somePublisher": [
+                                {
+                                    "name": "someName",
+                                    "topic": ["stress", "fear"]
+                                }
+                            ]
+                        },
+                        "user": {
+                            "formatedPublisher": [
+                                {
+                                    "name": "formatedPub1",
+                                    "segments": [
+                                        { "name": "segName1", "value": "segVal1" },
+                                        { "name": "segName2", "value": "segVal2" },
+                                        ["someKeyword"],
+                                        "stringKey"
+                                    ],
+                                    "bottom": ["bottomKey1", "bottomKey2"]
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "body": {
+                "ext": {
+                    "keywords": {
+                        "site": {
+                            "anotherSiteKeys": [
+                                {
+                                    "name":  "someName3",
+                                    "segments": [
+                                        { "name": "keyName", "value": "keyVal" }
+                                    ]
+                                }
+                            ],
+                            "somePublisher": [
+                                {
+                                    "name": "someName",
+                                    "segments": [
+                                        { "name": "topic", "value": "stress" },
+                                        { "name": "topic", "value": "fear" }
+                                    ]
+                                },
+                                {
+                                    "name": "someName2",
+                                    "segments": [
+                                        { "name": "topic", "value": "anyKey" }
+                                    ]
+                                }
+                            ],
+                            "formatedSitePublisher": [
+                                {
+                                    "name": "formatedPub2",
+                                    "segments": [
+                                        { "name": "segName1", "value": "segVal1" },
+                                        { "name": "segName2", "value": "segVal2" }
+                                    ]
+                                },
+                                {
+                                    "name": "notFormatedPub",
+                                    "segments": [
+                                        { "name": "topic2", "value": "notFormatedKw" }
+                                    ]
+                                }
+                            ],
+                            "ortb2": [
+                                {
+                                    "name": "keywords",
+                                    "segments": [
+                                        { "name": "keywords", "value": "siteKey1" },
+                                        { "name": "keywords", "value": "siteKey2" }
+                                    ]
+                                }
+                            ]
+                        },
+                        "user": {
+                            "ortb2": [
+                                {
+                                    "name": "keywords",
+                                    "segments": [
+                                        { "name": "keywords", "value": "userKey1" }
+                                    ]
+                                }
+                            ],
+                            "formatedUserPublisher": [
+                                {
+                                    "name": "formatedPub2",
+                                    "segments": [
+                                        { "name": "segName1", "value": "segVal1" },
+                                        { "name": "segName2", "value": "segVal2" }
+                                    ]
+                                }
+                            ],
+                            "formatedPublisher": [
+                                {
+                                    "name": "formatedPub1",
+                                    "segments": [
+                                        { "name": "segName1", "value": "segVal1" },
+                                        { "name": "segName2", "value": "segVal2" },
+                                        { "name": "bottom", "value": "bottomKey1" },
+                                        { "name": "bottom", "value": "bottomKey2" },
+                                        { "name": "segments", "value": "stringKey" }
+                                    ]
+                                }
+                            ]
+                        },
+                        "wrongKeys1": {
+                            "someKey1": "someVal1",
+                            "someKey2": "someVal2",
+                            "someKey3": ["someVal31", "someVal32"],
+                            "someKey4": { "key1": "val1", "key2": "val2" }
+                        },
+                        "stringKey": "stringVal",
+                        "anotherKeys": [
+                            {"someKey1":  "someVal1"},
+                            {"someKey2":  "someVal2"}
+                        ]
+                    }
+                },
+                "id": "test-request-id",
+                "site": {
+                    "page": "https://good.site/url",
+                    "keywords": "siteKey1,siteKey2"
+                },
+                "user": {
+                    "keywords": "userKey1"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {
+                            "uid": 1,
+                            "keywords": {
+                                "site": {
+                                    "somePublisher": [
+                                        {
+                                            "name": "someName",
+                                            "topic": ["stress", "fear"]
+                                        }
+                                    ]
+                                },
+                                "user": {
+                                    "formatedPublisher": [
+                                        {
+                                            "name": "formatedPub1",
+                                            "segments": [
+                                                { "name": "segName1", "value": "segVal1" },
+                                                { "name": "segName2", "value": "segVal2" },
+                                                ["someKeyword"],
+                                                "stringKey"
+                                            ],
+                                            "bottom": ["bottomKey1", "bottomKey2"]
+                                        }
+                                    ]
+                                }
+                            }
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "grid",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/grid/gridtest/exemplary/with-site-keywords.json b/adapters/grid/gridtest/exemplary/with-site-keywords.json
new file mode 100644
index 00000000..bbdf830a
--- /dev/null
+++ b/adapters/grid/gridtest/exemplary/with-site-keywords.json
@@ -0,0 +1,104 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url",
+            "keywords": "siteKey1,siteKey2"
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "uid": 1
+                }
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "body": {
+                "ext": {
+                    "keywords": {
+                        "site": {
+                            "ortb2": [
+                                {
+                                    "name": "keywords",
+                                    "segments": [
+                                        { "name": "keywords", "value": "siteKey1" },
+                                        { "name": "keywords", "value": "siteKey2" }
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                },
+                "id": "test-request-id",
+                "site": {
+                    "page": "https://good.site/url",
+                    "keywords": "siteKey1,siteKey2"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {
+                            "uid": 1
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "grid",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/grid/gridtest/exemplary/with-siteuser-keywords.json b/adapters/grid/gridtest/exemplary/with-siteuser-keywords.json
new file mode 100644
index 00000000..7a4822f8
--- /dev/null
+++ b/adapters/grid/gridtest/exemplary/with-siteuser-keywords.json
@@ -0,0 +1,120 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url",
+            "keywords": "siteKey1,siteKey2"
+        },
+        "user": {
+            "keywords": "userKey1"
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "uid": 1
+                }
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "body": {
+                "ext": {
+                    "keywords": {
+                        "site": {
+                            "ortb2": [
+                                {
+                                    "name": "keywords",
+                                    "segments": [
+                                        { "name": "keywords", "value": "siteKey1" },
+                                        { "name": "keywords", "value": "siteKey2" }
+                                    ]
+                                }
+                            ]
+                        },
+                        "user": {
+                            "ortb2": [
+                                {
+                                    "name": "keywords",
+                                    "segments": [
+                                        { "name": "keywords", "value": "userKey1" }
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                },
+                "id": "test-request-id",
+                "site": {
+                    "page": "https://good.site/url",
+                    "keywords": "siteKey1,siteKey2"
+                },
+                "user": {
+                    "keywords": "userKey1"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {
+                            "uid": 1
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "grid",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/grid/gridtest/exemplary/with-user-keywords.json b/adapters/grid/gridtest/exemplary/with-user-keywords.json
new file mode 100644
index 00000000..dbb8a7fe
--- /dev/null
+++ b/adapters/grid/gridtest/exemplary/with-user-keywords.json
@@ -0,0 +1,109 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url"
+        },
+        "user": {
+            "keywords": "userKey1,userKey2,userKey3"
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {
+                    "uid": 1
+                }
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid",
+            "body": {
+                "ext": {
+                    "keywords": {
+                        "user": {
+                            "ortb2": [
+                                {
+                                    "name": "keywords",
+                                    "segments": [
+                                        { "name": "keywords", "value": "userKey1" },
+                                        { "name": "keywords", "value": "userKey2" },
+                                        { "name": "keywords", "value": "userKey3" }
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                },
+                "id": "test-request-id",
+                "site": {
+                    "page": "https://good.site/url"
+                },
+                "user": {
+                    "keywords": "userKey1,userKey2,userKey3"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {
+                            "uid": 1
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "grid",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "some-test-ad",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "some-test-ad",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/grid/gridtest/params/race/banner.json b/adapters/grid/gridtest/params/race/banner.json
index 7e347f11..f64051bd 100644
--- a/adapters/grid/gridtest/params/race/banner.json
+++ b/adapters/grid/gridtest/params/race/banner.json
@@ -1,4 +1,14 @@
 {
-  "uid": 1
+  "uid": 1,
+  "keywords": {
+    "site": {
+      "somePublisher": [
+        {
+          "name": "someName",
+          "topic": ["stress", "fear"]
+        }
+      ]
+    }
+  }
 }
 
diff --git a/adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json b/adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json
new file mode 100644
index 00000000..d50ab6a7
--- /dev/null
+++ b/adapters/gumgum/gumgumtest/supplemental/banner-with-slot-param.json
@@ -0,0 +1,107 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 300
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zone": "dc9d6be1",
+            "slot": 12345678
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://g2.gumgum.com/providers/prbds2s/bid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 300
+                  }
+                ],
+                "w": 300,
+                "h": 250,
+                "ext": {
+                  "si": 12345678,
+                  "maxw": 300,
+                  "maxh": 300
+                }
+              },
+              "ext": {
+                "bidder": {
+                  "zone": "dc9d6be1",
+                  "slot": 12345678
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "crid": "2068416",
+                  "adm": "some-test-ad",
+                  "adid": "2068416",
+                  "price": 5,
+                  "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05",
+                  "impid": "test-imp-id",
+                  "cid": "4747"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "crid": "2068416",
+            "adm": "some-test-ad",
+            "adid": "2068416",
+            "price": 5,
+            "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05",
+            "impid": "test-imp-id",
+            "cid": "4747"
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/gumgum/params_test.go b/adapters/gumgum/params_test.go
index 726b6961..a578ce65 100644
--- a/adapters/gumgum/params_test.go
+++ b/adapters/gumgum/params_test.go
@@ -36,6 +36,8 @@ var validParams = []string{
 	`{"zone":"dc9d6be1"}`,
 	`{"pubId":12345678}`,
 	`{"zone":"dc9d6be1", "pubId":12345678}`,
+	`{"zone":"dc9d6be1", "slot":1234567}`,
+	`{"pubId":12345678, "slot":1234567}`,
 	`{"pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`,
 	`{"zone":"dc9d6be1", "irisid": "iris_6f9285823a48bne5"}`,
 	`{"zone":"dc9d6be1", "pubId":12345678, "irisid": "iris_6f9285823a48bne5"}`,
@@ -55,6 +57,7 @@ var invalidParams = []string{
 	`{"zone": true}`,
 	`{"placementId": 1, "zone":"1234567"}`,
 	`{"pubId":"123456"}`,
+	`{"slot":123456}`,
 	`{"zone":"1234567", "irisid": ""}`,
 	`{"zone":"1234567", "irisid": 1234}`,
 	`{"irisid": "iris_6f9285823a48bne5"}`,
diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go
index 5746f123..607bd6d2 100644
--- a/adapters/interactiveoffers/interactiveoffers_test.go
+++ b/adapters/interactiveoffers/interactiveoffers_test.go
@@ -10,7 +10,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderInteractiveoffers, config.Adapter{
-		Endpoint: "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"})
+		Endpoint: "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json
index 946289e5..f4247547 100644
--- a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json
+++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json
@@ -35,7 +35,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+      "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
       "body": {
         "id": "test-request-id",
         "site": {
diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json
index 2f49d545..ec670d74 100644
--- a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json
+++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json
@@ -22,7 +22,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+      "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
       "body": {
         "id": "test-request-id",
         "site": {
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json
index 0d56311a..9dc9a161 100644
--- a/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json
@@ -22,7 +22,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+      "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
       "body": {
         "id": "test-request-id",
         "site": {
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json
index 9aaf12cb..767c25bc 100644
--- a/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json
@@ -22,7 +22,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+      "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
       "body": {
         "id": "test-request-id",
         "site": {
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json
index 222be912..25d8b21c 100644
--- a/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json
@@ -22,7 +22,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+      "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
       "body": {
         "id": "test-request-id",
         "site": {
diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json
index 7ae16d4a..b3f48c1f 100644
--- a/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json
+++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json
@@ -22,7 +22,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
+      "uri": "https://prebid-server.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04",
       "body": {
         "id": "test-request-id",
         "site": {
diff --git a/adapters/smarthub/params_test.go b/adapters/smarthub/params_test.go
new file mode 100644
index 00000000..7797d4a8
--- /dev/null
+++ b/adapters/smarthub/params_test.go
@@ -0,0 +1,58 @@
+package smarthub
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+	`{"partnerName":"partnertest", "seat":"9Q20EdGxzgWdfPYShScl", "token":"eKmw6alpP3zWQhRCe3flOpz0wpuwRFjW"}`,
+}
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderSmartHub, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected smarthub params: %s", validParam)
+		}
+	}
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`[]`,
+	`{}`,
+	`{"anyparam": "anyvalue"}`,
+	`{"partnerName":"partnertest"}`,
+	`{"seat":"9Q20EdGxzgWdfPYShScl"}`,
+	`{"token":"Y9Evrh40ejsrCR4EtidUt1cSxhJsz8X1"}`,
+	`{"seat":"9Q20EdGxzgWdfPYShScl", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`,
+	`{"partnerName":"partnertest", "token":"LNywdP2ebX5iETF8gvBeEoB6Cam64eeq"}`,
+	`{"partnerName":"partnertest", "seat":"9Q20EdGxzgWdfPYShScl"}`,
+	`{"partnerName":"", "seat":"", "token":""}`,
+	`{"partnerName":"", "seat":"9Q20EdGxzgWdfPYShScl", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`,
+	`{"partnerName":"partnertest", "seat":"9Q20EdGxzgWdfPYShScl", "token":""}`,
+	`{"partnerName":"partnertest", "seat":"", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`,
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderSmartHub, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
diff --git a/adapters/smarthub/smarthub_test.go b/adapters/smarthub/smarthub_test.go
new file mode 100644
index 00000000..9a715208
--- /dev/null
+++ b/adapters/smarthub/smarthub_test.go
@@ -0,0 +1,18 @@
+package smarthub
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderSmartHub, config.Adapter{
+		Endpoint: "http://{{.Host}}-prebid.smart-hub.io/?seat={{.AccountID}}&token={{.SourceId}}"})
+
+	assert.NoError(t, buildErr)
+	adapterstest.RunJSONBidderTest(t, "smarthubtest", bidder)
+}
diff --git a/adapters/smarthub/smarthubtest/exemplary/banner.json b/adapters/smarthub/smarthubtest/exemplary/banner.json
new file mode 100644
index 00000000..a8b231b0
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/exemplary/banner.json
@@ -0,0 +1,164 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "id",
+          "bidid": "id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "id",
+                  "impid": "id",
+                  "price": 0.1,
+                  "nurl": "http://test.com/nurl",
+                  "burl": "http://test.com/burl",
+                  "adm": "<span>Test</span>",
+                  "adomain": [
+                    "test.com"
+                  ],
+                  "cat": [
+                    "IAB1"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "w": 300,
+                  "h": 250,
+                  "ext": {
+                    "mediaType": "banner"
+                  }
+                }
+              ],
+              "seat": "seat"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "id",
+            "impid": "id",
+            "price": 0.1,
+            "nurl": "http://test.com/nurl",
+            "burl": "http://test.com/burl",
+            "adm": "<span>Test</span>",
+            "adomain": [
+              "test.com"
+            ],
+            "cat": [
+              "IAB1"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "mediaType": "banner"
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/exemplary/native.json b/adapters/smarthub/smarthubtest/exemplary/native.json
new file mode 100644
index 00000000..60c9472a
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/exemplary/native.json
@@ -0,0 +1,160 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "native": {
+          "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+          "ver": "1.1"
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "native": {
+                "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                "ver": "1.1"
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "id",
+          "bidid": "id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "id",
+                  "impid": "id",
+                  "price": 0.1,
+                  "nurl": "http://test.com/nurl",
+                  "burl": "http://test.com/burl",
+                  "adm": "{}",
+                  "adomain": [
+                    "test.com"
+                  ],
+                  "cat": [
+                    "IAB1"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "ext": {
+                    "mediaType": "native"
+                  }
+                }
+              ],
+              "seat": "seat"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "id",
+            "impid": "id",
+            "price": 0.1,
+            "nurl": "http://test.com/nurl",
+            "burl": "http://test.com/burl",
+            "adm": "{}",
+            "adomain": [
+              "test.com"
+            ],
+            "cat": [
+              "IAB1"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "ext": {
+              "mediaType": "native"
+            }
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/exemplary/video.json b/adapters/smarthub/smarthubtest/exemplary/video.json
new file mode 100644
index 00000000..f64e4fe1
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/exemplary/video.json
@@ -0,0 +1,200 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/ogg",
+            "video/webm"
+          ],
+          "minduration": 3,
+          "maxduration": 3000,
+          "protocols": [
+            2,
+            3,
+            5,
+            6,
+            7,
+            8
+          ],
+          "w": 480,
+          "h": 320,
+          "linearity": 1,
+          "playbackmethod": [
+            2
+          ],
+          "pos": 0
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/ogg",
+                  "video/webm"
+                ],
+                "minduration": 3,
+                "maxduration": 3000,
+                "protocols": [
+                  2,
+                  3,
+                  5,
+                  6,
+                  7,
+                  8
+                ],
+                "w": 480,
+                "h": 320,
+                "linearity": 1,
+                "playbackmethod": [
+                  2
+                ],
+                "pos": 0
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "id",
+          "bidid": "id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "id",
+                  "impid": "id",
+                  "price": 0.1,
+                  "nurl": "http://test.com/nurl",
+                  "burl": "http://test.com/burl",
+                  "adm": "<VAST>",
+                  "adomain": [
+                    "test.com"
+                  ],
+                  "cat": [
+                    "IAB1"
+                  ],
+                  "cid": "cid",
+                  "crid": "crid",
+                  "ext": {
+                    "mediaType": "video"
+                  }
+                }
+              ],
+              "seat": "seat"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "id",
+            "impid": "id",
+            "price": 0.1,
+            "nurl": "http://test.com/nurl",
+            "burl": "http://test.com/burl",
+            "adm": "<VAST>",
+            "adomain": [
+              "test.com"
+            ],
+            "cat": [
+              "IAB1"
+            ],
+            "cid": "cid",
+            "crid": "crid",
+            "ext": {
+              "mediaType": "video"
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/params/race/banner.json b/adapters/smarthub/smarthubtest/params/race/banner.json
new file mode 100644
index 00000000..8c05702c
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+  "partnerName": "partnertest",
+  "seat": "9Q20EdGxzgWdfPYShScl",
+  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+}
diff --git a/adapters/smarthub/smarthubtest/params/race/native.json b/adapters/smarthub/smarthubtest/params/race/native.json
new file mode 100644
index 00000000..8c05702c
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/params/race/native.json
@@ -0,0 +1,5 @@
+{
+  "partnerName": "partnertest",
+  "seat": "9Q20EdGxzgWdfPYShScl",
+  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+}
diff --git a/adapters/smarthub/smarthubtest/params/race/video.json b/adapters/smarthub/smarthubtest/params/race/video.json
new file mode 100644
index 00000000..8c05702c
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/params/race/video.json
@@ -0,0 +1,5 @@
+{
+  "partnerName": "partnertest",
+  "seat": "9Q20EdGxzgWdfPYShScl",
+  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+}
diff --git a/adapters/smarthub/smarthubtest/supplemental/bad-response.json b/adapters/smarthub/smarthubtest/supplemental/bad-response.json
new file mode 100644
index 00000000..423d144b
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/bad-response.json
@@ -0,0 +1,108 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": ""
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json
new file mode 100644
index 00000000..d40787a7
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid-0-bid.json
@@ -0,0 +1,118 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+          "bidid": "359da97d0384d8a14767029c18fd840d",
+          "seatbid": [
+            {
+              "bid": [],
+              "seat": "seat"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Array SeatBid[0].Bid cannot be empty",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json
new file mode 100644
index 00000000..1789e71f
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/empty-seatbid.json
@@ -0,0 +1,113 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "a1580f2f-be6d-11eb-a150-d094662c1c35",
+          "bidid": "359da97d0384d8a14767029c18fd840d",
+          "seatbid": [],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Array SeatBid cannot be empty",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json
new file mode 100644
index 00000000..f8e92d34
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-bidder-object.json
@@ -0,0 +1,49 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": []
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Error while unmarshaling bidder extension",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json
new file mode 100644
index 00000000..f84f4fa4
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/invalid-ext-object.json
@@ -0,0 +1,47 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": ""
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Bidder extension not provided or can't be unmarshalled",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/status-204.json b/adapters/smarthub/smarthubtest/supplemental/status-204.json
new file mode 100644
index 00000000..a72264d0
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/status-204.json
@@ -0,0 +1,102 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/status-400.json b/adapters/smarthub/smarthubtest/supplemental/status-400.json
new file mode 100644
index 00000000..7af75b06
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/status-400.json
@@ -0,0 +1,108 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": "The Key has a different ad format"
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Request. \"The Key has a different ad format\"",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/status-503.json b/adapters/smarthub/smarthubtest/supplemental/status-503.json
new file mode 100644
index 00000000..d2cc83dd
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/status-503.json
@@ -0,0 +1,107 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 503
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bidder unavailable. Please contact the bidder support.",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/smarthubtest/supplemental/unexpected-status.json b/adapters/smarthub/smarthubtest/supplemental/unexpected-status.json
new file mode 100644
index 00000000..b2f1c76e
--- /dev/null
+++ b/adapters/smarthub/smarthubtest/supplemental/unexpected-status.json
@@ -0,0 +1,108 @@
+{
+  "mockBidRequest": {
+    "id": "id",
+    "imp": [
+      {
+        "id": "id",
+        "secure": 1,
+        "bidfloor": 0.01,
+        "bidfloorcur": "USD",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "partnerName": "partnertest",
+            "seat": "9Q20EdGxzgWdfPYShScl",
+            "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "UA",
+      "ip": "123.3.4.123"
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 0
+      }
+    },
+    "user": {
+      "id": "userid"
+    },
+    "site": {
+      "id": "id",
+      "domain": "test,com",
+      "cat": [
+        "IAB12"
+      ],
+      "publisher": {
+        "id": "pubid"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://partnertest-prebid.smart-hub.io/?seat=9Q20EdGxzgWdfPYShScl&token=zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer",
+        "body": {
+          "id": "id",
+          "imp": [
+            {
+              "id": "id",
+              "secure": 1,
+              "bidfloor": 0.01,
+              "bidfloorcur": "USD",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "partnerName": "partnertest",
+                  "seat": "9Q20EdGxzgWdfPYShScl",
+                  "token": "zpl5iB5Ugpe9ofVTzi44WzfjZZYq1yer"
+                }
+              }
+            }
+          ],
+          "device": {
+            "ua": "UA",
+            "ip": "123.3.4.123"
+          },
+          "regs": {
+            "ext": {
+              "gdpr": 0
+            }
+          },
+          "user": {
+            "id": "userid"
+          },
+          "site": {
+            "id": "id",
+            "domain": "test,com",
+            "cat": [
+              "IAB12"
+            ],
+            "publisher": {
+              "id": "pubid"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 403,
+        "body": "Access is denied"
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Status Code: [ 403 ] \"Access is denied\"",
+      "comparison": "literal"
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/smarthub/usersync_test.go b/adapters/smarthub/usersync_test.go
new file mode 100644
index 00000000..b834b21b
--- /dev/null
+++ b/adapters/smarthub/usersync_test.go
@@ -0,0 +1,33 @@
+package smarthub
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/ccpa"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewSmartHubSyncer(t *testing.T) {
+	syncURL := "https://test.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}"
+	syncURLTemplate := template.Must(
+		template.New("sync-template").Parse(syncURL),
+	)
+	syncer := NewSmartHubSyncer(syncURLTemplate)
+	syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal:  "0",
+			Consent: "allGdpr",
+		},
+		CCPA: ccpa.Policy{
+			Consent: "1---",
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, "https://test.com/pserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL)
+	assert.Equal(t, "redirect", syncInfo.Type)
+	assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json
new file mode 100644
index 00000000..bd9e9110
--- /dev/null
+++ b/adapters/yieldmo/yieldmotest/exemplary/with_gpid.json
@@ -0,0 +1,105 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "123"
+          },
+	  "data": {
+		"adserver": {
+			"name": "adserver_name",
+			"adslot": "adserver_adslot"
+		},
+		"pbadslot": "pbadslot_1"
+	  }
+        }
+      }
+    ],
+    "site": {
+      "id": "fake-site-id"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://ads.yieldmo.com/openrtb2",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              },
+              "ext": {
+                "placement_id": "123",
+		"gpid": "pbadslot_1"
+              }
+            }
+          ],
+          "site": {
+            "id": "fake-site-id"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "yieldmo",
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-imp-id",
+                  "price": 0.500000,
+                  "adm": "some-test-ad",
+                  "crid": "crid_10",
+                  "h": 250,
+                  "w": 300
+                }
+              ]
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 4835dd92..ec373094 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -18,7 +18,6 @@ import (
 	"time"
 
 	"github.com/buger/jsonparser"
-	jsonpatch "github.com/evanphx/json-patch"
 	"github.com/mxmCherry/openrtb/v15/native1"
 	nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request"
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
@@ -1020,8 +1019,10 @@ func TestStoredRequests(t *testing.T) {
 		hardcodedResponseIPValidator{response: true},
 	}
 
+	testStoreVideoAttr := []bool{true, true, false, false}
+
 	for i, requestData := range testStoredRequests {
-		newRequest, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData))
+		newRequest, impExtInfoMap, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData))
 		if len(errList) != 0 {
 			for _, err := range errList {
 				if err != nil {
@@ -1032,9 +1033,50 @@ func TestStoredRequests(t *testing.T) {
 			}
 		}
 		expectJson := json.RawMessage(testFinalRequests[i])
-		if !jsonpatch.Equal(newRequest, expectJson) {
-			t.Errorf("Error in processStoredRequests, test %d failed on compare\nFound:\n%s\nExpected:\n%s", i, string(newRequest), string(expectJson))
+
+		assert.JSONEq(t, string(expectJson), string(newRequest), "Incorrect result request %d", i)
+
+		expectedImp := testStoredImpIds[i]
+		expectedStoredImp := json.RawMessage(testStoredImps[i])
+		if len(impExtInfoMap[expectedImp].StoredImp) > 0 {
+			assert.JSONEq(t, string(expectedStoredImp), string(impExtInfoMap[expectedImp].StoredImp), "Incorrect expected stored imp %d", i)
+
 		}
+		assert.Equalf(t, testStoreVideoAttr[i], impExtInfoMap[expectedImp].EchoVideoAttrs, "EchoVideoAttrs value is incorrect")
+	}
+}
+
+func TestStoredRequestsVideoErrors(t *testing.T) {
+	deps := &endpointDeps{
+		&nobidExchange{},
+		newParamsValidator(t),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		&config.Configuration{MaxRequestSize: maxSize},
+		newTestMetrics(),
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BuildBidderMap(),
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+	}
+
+	// tests processStoredRequests function behavior in parsing incorrect input related to echovideoattrs feature
+	// this test now has 2 scenarios:
+	// 1) expected value we get using jsonparser.GetBoolean is integer
+	// 2) imp id is not found using jsonparser.GetString
+	// this loop iterates over testStoredRequestsErrors where input json has incorrect set up
+	// testStoredRequestsErrorsResults variable contains error message for every iteration
+
+	for i, requestData := range testStoredRequestsErrors {
+		_, _, errList := deps.processStoredRequests(context.Background(), json.RawMessage(requestData))
+
+		assert.NotEmpty(t, errList, "processStoredRequests should return error")
+		assert.Contains(t, errList[0].Error(), testStoredRequestsErrorsResults[i], "Incorrect error")
 	}
 }
 
@@ -2902,7 +2944,24 @@ var testStoredRequestData = map[string]json.RawMessage{
 // third below has valid JSON and matches schema
 var testStoredImpData = map[string]json.RawMessage{
 	"1": json.RawMessage(`{
-"id": "adUnit1",
+			"id": "adUnit1",
+			"ext": {
+				"appnexus": {
+					"placementId": "abc",
+					"position": "above",
+					"reserve": 0.35
+				},
+				"rubicon": {
+					"accountId": "abc"
+				}
+			},
+			"video":{
+				"w":200,
+				"h":300
+			}
+		}`),
+	"2": json.RawMessage(`{
+			"id": "adUnit1",
 			"ext": {
 				"appnexus": {
 					"placementId": "abc",
@@ -2915,7 +2974,7 @@ var testStoredImpData = map[string]json.RawMessage{
 			}
 		}`),
 	"7": json.RawMessage(`{
-"id": "adUnit1",
+			"id": "adUnit1",
 			"ext": {
 				"appnexus": {
 					"placementId": 12345678,
@@ -2944,6 +3003,15 @@ var testStoredImpData = map[string]json.RawMessage{
 				}
 			}
 		}`),
+	"10": json.RawMessage(`{
+			"ext": {
+				"appnexus": {
+					"placementId": 12345678,
+					"position": "above",
+					"reserve": 0.35
+				}
+			}
+		}`),
 }
 
 // Incoming requests with stored request IDs
@@ -2952,10 +3020,17 @@ var testStoredRequests = []string{
 		"id": "ThisID",
 		"imp": [
 			{
+				"video":{
+					"h":300,
+					"w":200
+				},
 				"ext": {
 					"prebid": {
 						"storedrequest": {
 							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": true
 						}
 					}
 				}
@@ -2980,6 +3055,9 @@ var testStoredRequests = []string{
 					"prebid": {
 						"storedrequest": {
 							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": true
 						}
 					},
 					"appnexus": {
@@ -3008,7 +3086,10 @@ var testStoredRequests = []string{
 				"ext": {
 					"prebid": {
 						"storedrequest": {
-							"id": "1"
+							"id": "2"
+						},
+						"options": {
+							"echovideoattrs": false
 						}
 					}
 				}
@@ -3059,25 +3140,22 @@ var testStoredRequests = []string{
 	}`,
 }
 
-// The expected requests after stored request processing
-var testFinalRequests = []string{
+var testStoredRequestsErrors = []string{
 	`{
 		"id": "ThisID",
 		"imp": [
 			{
-				"id": "adUnit1",
+				"video":{
+					"h":300,
+					"w":200
+				},
 				"ext": {
-					"appnexus": {
-						"placementId": "abc",
-						"position": "above",
-						"reserve": 0.35
-					},
-					"rubicon": {
-						"accountId": "abc"
-					},
 					"prebid": {
 						"storedrequest": {
 							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": 1
 						}
 					}
 				}
@@ -3092,22 +3170,22 @@ var testFinalRequests = []string{
 				}
 			}
 		}
-	}`,
-	`{
+	}`, `{
 		"id": "ThisID",
 		"imp": [
 			{
-				"id": "adUnit2",
+				"video":{
+					"h":300,
+					"w":200
+				},
 				"ext": {
 					"prebid": {
 						"storedrequest": {
-							"id": "1"
+							"id": "10"
+						},
+						"options": {
+							"echovideoattrs": true
 						}
-					},
-					"appnexus": {
-						"placementId": "def",
-						"position": "above",
-						"trafficSourceCode": "mysite.com"
 					}
 				}
 			}
@@ -3122,56 +3200,220 @@ var testFinalRequests = []string{
 			}
 		}
 	}`,
+}
+
+var testStoredRequestsErrorsResults = []string{
+	"Value is not a boolean: 1",
+	"Key path not found",
+}
+
+// The expected requests after stored request processing
+var testFinalRequests = []string{
 	`{
 		"id": "ThisID",
 		"imp": [
 			{
-				"id": "adUnit1",
-				"ext": {
-					"appnexus": {
-						"placementId": "abc",
-						"position": "above",
-						"reserve": 0.35
-					},
-					"rubicon": {
-						"accountId": "abc"
+				"video":{
+					"h":300,
+					"w":200
+				},
+				"ext":{
+					"appnexus":{
+						"placementId":"abc",
+						"position":"above",
+						"reserve":0.35
 					},
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						}
+					"prebid":{
+						"storedrequest":{
+							"id":"1"
+						},
+					"options":{
+						"echovideoattrs":true
 					}
+				},
+				"rubicon":{
+					"accountId":"abc"
 				}
+			},
+			"id":"adUnit1"
 			}
 		],
-		"tmax": 500,
 		"ext": {
 			"prebid": {
+				"cache": {
+					"markup": 1
+				},
 				"targeting": {
-					"pricegranularity": "low"
+			}
+		}
+}
+	}`,
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"video":{
+					"w":200,
+					"h":300
+				},
+				"ext":{
+					"appnexus":{
+						"placementId":"def",
+						"position":"above",
+						"trafficSourceCode":"mysite.com"
+					},
+					"prebid":{
+						"storedrequest":{
+							"id":"1"
+						},
+						"options":{
+							"echovideoattrs":true
+						}
+					}
 				},
-				"storedrequest": {
-					"id": "2"
+				"id":"adUnit2"
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
 				}
 			}
 		}
 	}`,
+	`{
+  		"ext": {
+  		  "prebid": {
+  		    "storedrequest": {
+  		      "id": "2"
+  		    },
+  		    "targeting": {
+  		      "pricegranularity": "low"
+  		    }
+  		  }
+  		},
+  		"id": "ThisID",
+  		"imp": [
+  		  {
+  		    "ext": {
+  		      "appnexus": {
+  		        "placementId": "abc",
+  		        "position": "above",
+  		        "reserve": 0.35
+  		      },
+  		      "prebid": {
+  		        "storedrequest": {
+  		          "id": "2"
+  		        },
+  		        "options":{
+					"echovideoattrs":false
+				}
+  		      },
+  		      "rubicon": {
+  		        "accountId": "abc"
+  		      }
+  		    },
+  		    "id": "adUnit1"
+  		  }
+  		],
+  		"tmax": 500
+	}
+`,
 	`{
 	"id": "ThisID",
 	"imp": [
 		{
-			"id": "some-static-imp",
-			"video":{
-				"mimes":["video/mp4"]
+    		"id": "some-static-imp",
+    		"video": {
+    		  "mimes": [
+    		    "video/mp4"
+    		  ]
+    		},
+    		"ext": {
+    		  "appnexus": {
+    		    "placementId": "abc",
+    		    "position": "below"
+    		  }
+    		}
+  		},
+  		{
+  		  "ext": {
+  		    "appnexus": {
+  		      "placementId": "abc",
+  		      "position": "above",
+  		      "reserve": 0.35
+  		    },
+  		    "prebid": {
+  		      "storedrequest": {
+  		        "id": "1"
+  		      }
+  		    },
+  		    "rubicon": {
+  		      "accountId": "abc"
+  		    }
+  		  },
+  		  "id": "adUnit1",
+		  "video":{
+				"w":200,
+				"h":300
+          }
+  		}
+	],
+	"ext": {
+		"prebid": {
+			"cache": {
+				"markup": 1
 			},
-			"ext": {
-				"appnexus": {
-					"placementId": "abc",
-					"position": "below"
-				}
+			"targeting": {
 			}
-		},
-		{
+		}
+	}
+}`,
+}
+
+var testStoredImpIds = []string{
+	"adUnit1", "adUnit2", "adUnit1", "some-static-imp",
+}
+
+var testStoredImps = []string{
+	`{
+		"id": "adUnit1",
+        "ext": {
+        	"appnexus": {
+        		"placementId": "abc",
+        		"position": "above",
+        		"reserve": 0.35
+        	},
+        	"rubicon": {
+        		"accountId": "abc"
+        	}
+        },
+		"video":{
+        	"w":200,
+        	"h":300
+		}
+	}`,
+	`{
+		"id": "adUnit1",
+        "ext": {
+        	"appnexus": {
+        		"placementId": "abc",
+        		"position": "above",
+        		"reserve": 0.35
+        	},
+        	"rubicon": {
+        		"accountId": "abc"
+        	}
+        },
+		"video":{
+        	"w":200,
+        	"h":300
+		}
+	}`,
+	`{
 			"id": "adUnit1",
 			"ext": {
 				"appnexus": {
@@ -3181,25 +3423,10 @@ var testFinalRequests = []string{
 				},
 				"rubicon": {
 					"accountId": "abc"
-				},
-				"prebid": {
-					"storedrequest": {
-						"id": "1"
-					}
 				}
 			}
-		}
-	],
-	"ext": {
-		"prebid": {
-			"cache": {
-				"markup": 1
-			},
-			"targeting": {
-			}
-		}
-	}
-}`,
+		}`,
+	``,
 }
 
 type mockStoredReqFetcher struct {
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 989e6707..aa444352 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -149,7 +149,7 @@ func TestCharacterEscape(t *testing.T) {
 	var errList []error
 
 	// 	4) Build bid response
-	bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, errList)
+	bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, errList)
 
 	// 	5) Assert we have no errors and one '&' character as we are supposed to
 	if err != nil {
@@ -1307,7 +1307,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	var errList []error
 
 	// 	4) Build bid response
-	bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, errList)
+	bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, errList)
 
 	// 	5) Assert we have no errors and the bid response we expected
 	assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error")
@@ -1400,7 +1400,7 @@ func TestBidReturnsCreative(t *testing.T) {
 
 	//Run tests
 	for _, test := range testCases {
-		resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative)
+		resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil)
 
 		assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description)
 		assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description)
@@ -1685,12 +1685,64 @@ func TestBidResponseCurrency(t *testing.T) {
 	}
 	// Run tests
 	for i := range testCases {
-		actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, errList)
+		actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, errList)
 		assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err))
 		assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext))
 	}
 }
 
+func TestBidResponseImpExtInfo(t *testing.T) {
+	// Init objects
+	cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)}
+	cfg.Adapters["appnexus"] = config.Adapter{Endpoint: "http://ib.adnxs.com"}
+
+	e := NewExchange(nil, nil, cfg, &metricsConf.DummyMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange)
+
+	liveAdapters := make([]openrtb_ext.BidderName, 1)
+	liveAdapters[0] = "appnexus"
+
+	bidRequest := &openrtb2.BidRequest{
+		ID: "some-request-id",
+		Imp: []openrtb2.Imp{{
+			ID:    "some-impression-id",
+			Video: &openrtb2.Video{},
+			Ext:   json.RawMessage(`{"appnexus": {"placementId": 10433394}}`),
+		}},
+		Ext: json.RawMessage(``),
+	}
+
+	var errList []error
+
+	sampleBid := &openrtb2.Bid{
+		ID:    "some-imp-id",
+		ImpID: "some-impression-id",
+		W:     300,
+		H:     250,
+		Ext:   nil,
+	}
+	aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeVideo}}
+
+	adapterBids := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+		openrtb_ext.BidderName("appnexus"): {
+			bids: aPbsOrtbBidArr,
+		},
+	}
+
+	impExtInfo := make(map[string]ImpExtInfo, 1)
+	impExtInfo["some-impression-id"] = ImpExtInfo{
+		true,
+		[]byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}
+
+	expectedBidResponseExt := `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`
+
+	actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, errList)
+	assert.NoError(t, err, fmt.Sprintf("imp ext info was not passed through correctly: %s", err))
+
+	resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext)
+	assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect")
+
+}
+
 // TestRaceIntegration runs an integration test using all the sample params from
 // adapters/{bidder}/{bidder}test/params/race/*.json files.
 //
@@ -2359,10 +2411,10 @@ func TestCategoryMapping(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -2414,10 +2466,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -2468,9 +2520,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -2550,9 +2602,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -2602,11 +2654,11 @@ func TestCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_5 := pbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2681,11 +2733,11 @@ func TestNoCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_5 := pbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2761,8 +2813,8 @@ func TestCategoryMappingBidderName(t *testing.T) {
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids1 := []*pbsOrtbBid{
 		&bid1_1,
@@ -2815,8 +2867,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_1 := pbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids1 := []*pbsOrtbBid{
 		&bid1_1,
@@ -2927,7 +2979,7 @@ func TestBidRejectionErrors(t *testing.T) {
 		innerBids := []*pbsOrtbBid{}
 		for _, bid := range test.bids {
 			currentBid := pbsOrtbBid{
-				bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""}
+				bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""}
 			innerBids = append(innerBids, &currentBid)
 		}
 
@@ -2975,8 +3027,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
 	bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn1 := pbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn2 := pbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBidsApn1 := []*pbsOrtbBid{
 		&bid1_Apn1,
@@ -3055,11 +3107,11 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T)
 	bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 	bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
-	bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBidsApn1 := []*pbsOrtbBid{
 		&bid1_Apn1_1,
@@ -3137,9 +3189,9 @@ func TestRemoveBidById(t *testing.T) {
 	bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 	bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
-	bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn1_1 := pbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	type aTest struct {
 		desc      string
@@ -3264,7 +3316,7 @@ func TestApplyDealSupport(t *testing.T) {
 			},
 		}
 
-		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""}
+		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""}
 		bidCategory := map[string]string{
 			bid.bid.ID: test.targ["hb_pb_cat_dur"],
 		}
@@ -3431,7 +3483,7 @@ func TestUpdateHbPbCatDur(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""}
+		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""}
 		bidCategory := map[string]string{
 			bid.bid.ID: test.targ["hb_pb_cat_dur"],
 		}
@@ -3443,6 +3495,113 @@ func TestUpdateHbPbCatDur(t *testing.T) {
 	}
 }
 
+func TestMakeBidExtJSON(t *testing.T) {
+
+	type aTest struct {
+		description        string
+		ext                json.RawMessage
+		extBidPrebid       openrtb_ext.ExtBidPrebid
+		impExtInfo         map[string]ImpExtInfo
+		expectedBidExt     string
+		expectedErrMessage string
+	}
+
+	testCases := []aTest{
+		{
+			description:        "Valid extension, non empty extBidPrebid and valid imp ext info",
+			ext:                json.RawMessage(`{"video":{"h":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}},
+			expectedBidExt:     `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Empty extension, non empty extBidPrebid and valid imp ext info",
+			ext:                nil,
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}},
+			expectedBidExt:     `{"prebid":{"type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Valid extension, non empty extBidPrebid and imp ext info not found",
+			ext:                json.RawMessage(`{"video":{"h":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}},
+			expectedBidExt:     `{"prebid":{"type":"video"},"video":{"h":100}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Valid extension, empty extBidPrebid and valid imp ext info",
+			ext:                json.RawMessage(`{"video":{"h":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}},
+			expectedBidExt:     `{"prebid":{"type":""},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Valid extension, non empty extBidPrebid and empty imp ext info",
+			ext:                json.RawMessage(`{"video":{"h":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         nil,
+			expectedBidExt:     `{"prebid":{"type":"video"},"video":{"h":100}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Valid extension, non empty extBidPrebid and valid imp ext info without video attr",
+			ext:                json.RawMessage(`{"video":{"h":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`)}},
+			expectedBidExt:     `{"prebid":{"type":"video"},"video":{"h":100}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Valid extension with prebid, non empty extBidPrebid and valid imp ext info without video attr",
+			ext:                json.RawMessage(`{"prebid":{"targeting":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`)}},
+			expectedBidExt:     `{"prebid":{"type":"video"}}`,
+			expectedErrMessage: "",
+		},
+		{
+			description:        "Valid extension with prebid, non empty extBidPrebid and valid imp ext info with video attr",
+			ext:                json.RawMessage(`{"prebid":{"targeting":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}},
+			expectedBidExt:     `{"prebid":{"type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`,
+			expectedErrMessage: "",
+		},
+		//Error cases
+		{
+			description:        "Invalid extension, valid extBidPrebid and valid imp ext info",
+			ext:                json.RawMessage(`{invalid json}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`)}},
+			expectedBidExt:     ``,
+			expectedErrMessage: "invalid character",
+		},
+		{
+			description:        "Valid extension, empty extBidPrebid and invalid imp ext info",
+			ext:                json.RawMessage(`{"video":{"h":100}}`),
+			extBidPrebid:       openrtb_ext.ExtBidPrebid{},
+			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{!}}`)}},
+			expectedBidExt:     ``,
+			expectedErrMessage: "invalid character",
+		},
+	}
+
+	for _, test := range testCases {
+		result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id")
+
+		if test.expectedErrMessage == "" {
+			assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result")
+			assert.NoError(t, err, "Error should not be returned")
+		} else {
+			assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message")
+		}
+	}
+}
+
 type exchangeSpec struct {
 	GDPREnabled       bool                   `json:"gdpr_enabled"`
 	IncomingRequest   exchangeRequest        `json:"incomingRequest"`
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 342d2ae8..7eacd086 100644
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -85,6 +85,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderRubicon):          syncConfig,
 			string(openrtb_ext.BidderSharethrough):     syncConfig,
 			string(openrtb_ext.BidderSmartAdserver):    syncConfig,
+			string(openrtb_ext.BidderSmartHub):         syncConfig,
 			string(openrtb_ext.BidderSmartRTB):         syncConfig,
 			string(openrtb_ext.BidderSmartyAds):        syncConfig,
 			string(openrtb_ext.BidderSmileWanted):      syncConfig,
diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go
index 0b6ec34c..d8738e17 100644
--- a/util/jsonutil/jsonutil_test.go
+++ b/util/jsonutil/jsonutil_test.go
@@ -7,7 +7,6 @@ import (
 )
 
 func TestDropElement(t *testing.T) {
-
 	tests := []struct {
 		description     string
 		input           []byte
@@ -17,7 +16,7 @@ func TestDropElement(t *testing.T) {
 		errorContains   string
 	}{
 		{
-			description:     "Drop Single Element After Another Element",
+			description:     "Drop single element after another element",
 			input:           []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`),
 			elementToRemove: "consented_providers",
 			output:          []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`),
@@ -25,7 +24,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Single Element Before Another Element",
+			description:     "Drop single element before another element",
 			input:           []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`),
 			elementToRemove: "consented_providers",
 			output:          []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`),
@@ -33,7 +32,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Single Element",
+			description:     "Drop single element",
 			input:           []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`),
 			elementToRemove: "consented_providers",
 			output:          []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`),
@@ -41,7 +40,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Single Element string",
+			description:     "Drop single element string",
 			input:           []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`),
 			elementToRemove: "consented_providers",
 			output:          []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`),
@@ -49,7 +48,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Parent Element Between Two Elements",
+			description:     "Drop parent element between two elements",
 			input:           []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`),
 			elementToRemove: "consented_providers_settings",
 			output:          []byte(`{"consent": "TESTCONSENT","test": 123}`),
@@ -57,7 +56,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Parent Element Before Element",
+			description:     "Drop parent element before element",
 			input:           []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`),
 			elementToRemove: "consented_providers_settings",
 			output:          []byte(`{"test": 123}`),
@@ -65,7 +64,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Parent Element After Element",
+			description:     "Drop parent element after element",
 			input:           []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
 			elementToRemove: "consented_providers_settings",
 			output:          []byte(`{"consent": "TESTCONSENT"}`),
@@ -73,7 +72,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Parent Element Only",
+			description:     "Drop parent element only",
 			input:           []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
 			elementToRemove: "consented_providers_settings",
 			output:          []byte(`{}`),
@@ -81,7 +80,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "",
 		},
 		{
-			description:     "Drop Element That Doesn't Exist",
+			description:     "Drop element that doesn't exist",
 			input:           []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
 			elementToRemove: "test2",
 			output:          []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
@@ -90,7 +89,7 @@ func TestDropElement(t *testing.T) {
 		},
 		//Errors
 		{
-			description:     "Error Decode",
+			description:     "Error decode",
 			input:           []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`),
 			elementToRemove: "consented_providers",
 			output:          []byte(``),
@@ -98,7 +97,7 @@ func TestDropElement(t *testing.T) {
 			errorContains:   "looking for beginning of value",
 		},
 		{
-			description:     "Error Malformed",
+			description:     "Error malformed",
 			input:           []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`),
 			elementToRemove: "consented_providers",
 			output:          []byte(``),
@@ -120,3 +119,188 @@ func TestDropElement(t *testing.T) {
 
 	}
 }
+
+func TestFindElement(t *testing.T) {
+	tests := []struct {
+		description   string
+		input         []byte
+		elementToFind string
+		output        []byte
+		elementFound  bool
+	}{
+		{
+			description:   "Find array element",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers":[1608,765,492]}}`),
+			elementToFind: "consented_providers",
+			output:        []byte(`[1608,765,492]`),
+			elementFound:  true,
+		},
+		{
+			description:   "Find object element",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`),
+			elementToFind: "consented_providers_settings",
+			output:        []byte(`{"test": 1,"consented_providers": [1608,765,492]}`),
+			elementFound:  true,
+		},
+		{
+			description:   "Find element that doesn't exist",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings":{"test": 1,"consented_providers": [1608,765,492]}}`),
+			elementToFind: "test_element",
+			output:        []byte(nil),
+			elementFound:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		exists, res, err := FindElement(tt.input, tt.elementToFind)
+		assert.NoError(t, err, "Error should be nil")
+		assert.Equal(t, tt.output, res, "Result is incorrect")
+
+		if tt.elementFound {
+			assert.True(t, exists, "Element must be found")
+		} else {
+			assert.False(t, exists, "Element must not be found")
+		}
+	}
+}
+
+func TestFindElementIndexes(t *testing.T) {
+	tests := []struct {
+		description   string
+		input         []byte
+		elementToFind string
+		startIndex    int64
+		endIndex      int64
+		found         bool
+		errorExpected bool
+		errorContains string
+	}{
+		{
+			description:   "Find single element after another element",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`),
+			elementToFind: "consented_providers",
+			startIndex:    68,
+			endIndex:      106,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find single element before another element",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`),
+			elementToFind: "consented_providers",
+			startIndex:    59,
+			endIndex:      97,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find single element",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`),
+			elementToFind: "consented_providers",
+			startIndex:    59,
+			endIndex:      98,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find single element string",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`),
+			elementToFind: "consented_providers",
+			startIndex:    59,
+			endIndex:      88,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find parent element between two elements",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`),
+			elementToFind: "consented_providers_settings",
+			startIndex:    26,
+			endIndex:      109,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find parent element before element",
+			input:         []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`),
+			elementToFind: "consented_providers_settings",
+			startIndex:    1,
+			endIndex:      84,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find parent element after element",
+			input:         []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+			elementToFind: "consented_providers_settings",
+			startIndex:    25,
+			endIndex:      108,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find parent element only",
+			input:         []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+			elementToFind: "consented_providers_settings",
+			startIndex:    1,
+			endIndex:      83,
+			found:         true,
+			errorExpected: false,
+			errorContains: "",
+		},
+		{
+			description:   "Find element that doesn't exist",
+			input:         []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`),
+			elementToFind: "test2",
+			startIndex:    -1,
+			endIndex:      -1,
+			found:         false,
+			errorExpected: false,
+			errorContains: "",
+		},
+		//Errors
+		{
+			description:   "Error decode",
+			input:         []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`),
+			elementToFind: "consented_providers",
+			startIndex:    -1,
+			endIndex:      -1,
+			found:         false,
+			errorExpected: true,
+			errorContains: "looking for beginning of value",
+		},
+		{
+			description:   "Error malformed",
+			input:         []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`),
+			elementToFind: "consented_providers",
+			startIndex:    -1,
+			endIndex:      -1,
+			found:         false,
+			errorExpected: true,
+			errorContains: "invalid character",
+		},
+	}
+
+	for _, tt := range tests {
+		found, start, end, err := findElementIndexes(tt.input, tt.elementToFind)
+
+		assert.Equal(t, tt.found, found, "Incorrect value of element existence")
+
+		if tt.errorExpected {
+			assert.Error(t, err, "Error should not be nil")
+			assert.True(t, strings.Contains(err.Error(), tt.errorContains))
+		} else {
+			assert.NoError(t, err, "Error should be nil")
+			assert.Equal(t, tt.startIndex, start, "Result is incorrect")
+			assert.Equal(t, tt.endIndex, end, "Result is incorrect")
+		}
+
+	}
+}
diff --git a/util/maputil/maputil_test.go b/util/maputil/maputil_test.go
index 2e6955ce..abce102f 100644
--- a/util/maputil/maputil_test.go
+++ b/util/maputil/maputil_test.go
@@ -111,3 +111,56 @@ func TestReadEmbeddedSlice(t *testing.T) {
 		assert.Equal(t, test.expectedOK, resultOK, test.description+":ok")
 	}
 }
+
+func TestReadEmbeddedString(t *testing.T) {
+	testCases := []struct {
+		description    string
+		value          map[string]interface{}
+		key            string
+		expectedString string
+		expectedOK     bool
+	}{
+		{
+			description:    "Nil",
+			value:          nil,
+			key:            "",
+			expectedString: "",
+			expectedOK:     false,
+		},
+		{
+			description:    "Empty",
+			value:          map[string]interface{}{},
+			key:            "foo",
+			expectedString: "",
+			expectedOK:     false,
+		},
+		{
+			description:    "Success",
+			value:          map[string]interface{}{"foo": "stringValue"},
+			key:            "foo",
+			expectedString: "stringValue",
+			expectedOK:     true,
+		},
+		{
+			description:    "Not Found",
+			value:          map[string]interface{}{"foo": "stringValue"},
+			key:            "notFound",
+			expectedString: "",
+			expectedOK:     false,
+		},
+		{
+			description:    "Wrong Type",
+			value:          map[string]interface{}{"foo": []interface{}{42}},
+			key:            "foo",
+			expectedString: "",
+			expectedOK:     false,
+		},
+	}
+
+	for _, test := range testCases {
+		resultString, resultOK := ReadEmbeddedString(test.value, test.key)
+
+		assert.Equal(t, test.expectedString, resultString, test.description+":string")
+		assert.Equal(t, test.expectedOK, resultOK, test.description+":ok")
+	}
+}