Repository: prebid/prebid-server
Pull request #1303 (2020-05-15 22:55:45)

MobileFuse Adapter
This is for a new MobileFuse adapter. We are an in-app only buyer with support for banner and video. Thanks for your time!

dtbarne: @guscarreon I've submitted the requested edits, but the PR is still showing as "changes requested". I'm not sure if there's anything else I need to do to kick it back over to review.

Pardon my GitHub ignorance, I spend most of my time in Bitbucket.
dtbarne: Hello @SyntaxNode and @guscarreon, is there anything more that needs to be done to get this merged? Thanks!

Patch:
diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go
new file mode 100644
index 00000000..3351f10d
--- /dev/null
+++ b/adapters/mobilefuse/mobilefuse.go
@@ -0,0 +1,193 @@
+package mobilefuse
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/golang/glog"
+	"github.com/mxmCherry/openrtb"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"net/http"
+	"strconv"
+	"text/template"
+)
+
+type MobileFuseAdapter struct {
+	EndpointTemplate template.Template
+}
+
+func NewMobileFuseBidder(endpointTemplate string) adapters.Bidder {
+	parsedTemplate, err := template.New("endpointTemplate").Parse(endpointTemplate)
+
+	if err != nil {
+		glog.Fatal("Unable parse endpoint template: " + err.Error())
+		return nil
+	}
+
+	return &MobileFuseAdapter{EndpointTemplate: *parsedTemplate}
+}
+
+func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var adapterRequests []*adapters.RequestData
+
+	adapterRequest, errs := adapter.makeRequest(request)
+
+	if errs == nil {
+		adapterRequests = append(adapterRequests, adapterRequest)
+	}
+
+	return adapterRequests, errs
+}
+
+func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, outgoingRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode == http.StatusBadRequest {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode),
+		}}
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d.", response.StatusCode),
+		}}
+	}
+
+	var incomingBidResponse openrtb.BidResponse
+
+	if err := json.Unmarshal(response.Body, &incomingBidResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	outgoingBidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+
+	for _, seatbid := range incomingBidResponse.SeatBid {
+		for i := range seatbid.Bid {
+			outgoingBidResponse.Bids = append(outgoingBidResponse.Bids, &adapters.TypedBid{
+				Bid:     &seatbid.Bid[i],
+				BidType: adapter.getBidType(seatbid.Bid[i].ImpID, incomingRequest.Imp),
+			})
+		}
+	}
+
+	return outgoingBidResponse, nil
+}
+
+func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb.BidRequest) (*adapters.RequestData, []error) {
+	var errs []error
+
+	mobileFuseExtension, errs := adapter.getFirstMobileFuseExtension(bidRequest)
+
+	if errs != nil {
+		return nil, errs
+	}
+
+	endpoint, err := adapter.getEndpoint(mobileFuseExtension)
+
+	if err != nil {
+		return nil, append(errs, err)
+	}
+
+	validImps := adapter.getValidImps(bidRequest, mobileFuseExtension)
+
+	if len(validImps) == 0 {
+		err := fmt.Errorf("No valid imps")
+		errs = append(errs, err)
+		return nil, errs
+	}
+
+	mobileFuseBidRequest := *bidRequest
+	mobileFuseBidRequest.Imp = validImps
+	body, err := json.Marshal(mobileFuseBidRequest)
+
+	if err != nil {
+		return nil, append(errs, err)
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     endpoint,
+		Body:    body,
+		Headers: headers,
+	}, errs
+}
+
+func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) {
+	var mobileFuseImpExtension openrtb_ext.ExtImpMobileFuse
+	var errs []error
+
+	for _, imp := range request.Imp {
+		var bidder_imp_extension adapters.ExtImpBidder
+
+		err := json.Unmarshal(imp.Ext, &bidder_imp_extension)
+
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		err = json.Unmarshal(bidder_imp_extension.Bidder, &mobileFuseImpExtension)
+
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		break
+	}
+
+	return &mobileFuseImpExtension, errs
+}
+
+func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse) (string, error) {
+	publisher_id := strconv.Itoa(ext.PublisherId)
+
+	url, errs := macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{PublisherID: publisher_id})
+
+	if errs != nil {
+		return "", errs
+	}
+
+	if ext.TagidSrc == "ext" {
+		url += "&tagid_src=ext"
+	}
+
+	return url, nil
+}
+
+func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb.Imp {
+	var validImps []openrtb.Imp
+
+	for _, imp := range bidRequest.Imp {
+		if imp.Banner != nil || imp.Video != nil {
+			if imp.Banner != nil && imp.Video != nil {
+				imp.Video = nil
+			}
+
+			imp.TagID = strconv.Itoa(ext.PlacementId)
+			imp.Ext = nil
+			validImps = append(validImps, imp)
+
+			break
+		}
+	}
+
+	return validImps
+}
+
+func (adapter *MobileFuseAdapter) getBidType(imp_id string, imps []openrtb.Imp) openrtb_ext.BidType {
+	if imps[0].Video != nil {
+		return openrtb_ext.BidTypeVideo
+	}
+
+	return openrtb_ext.BidTypeBanner
+}
diff --git a/config/config.go b/config/config.go
index beb6e0a6..703606b2 100755
--- a/config/config.go
+++ b/config/config.go
@@ -729,6 +729,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet")
 	v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/")
+	v.SetDefault("adapters.mobilefuse.endpoint", "http://mfx-us-east.mobilefuse.com/openrtb?pub_id={{.PublisherID}}")
 	v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs")
 	v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}")
 	v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid")
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 97ac8d0c..586481dd 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -71,6 +71,7 @@ func NewAmpEndpoint(
 		defRequest,
 		defReqJSON,
 		bidderMap,
+		nil,
 		nil}).AmpAuction), nil
 
 }
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 4594a4d5..bcb13724 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -9,6 +9,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"regexp"
 	"strconv"
 	"time"
 
@@ -57,6 +58,7 @@ func NewEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamValidato
 		defRequest,
 		defReqJSON,
 		bidderMap,
+		nil,
 		nil}).Auction), nil
 }
 
@@ -74,6 +76,7 @@ type endpointDeps struct {
 	defReqJSON       []byte
 	bidderMap        map[string]openrtb_ext.BidderName
 	cache            prebid_cache_client.Client
+	debugLogRegexp   *regexp.Regexp
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index cf764bc9..020a5196 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -9,12 +9,13 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/url"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
 
 	"github.com/buger/jsonparser"
-	"github.com/evanphx/json-patch"
+	jsonpatch "github.com/evanphx/json-patch"
 	"github.com/gofrs/uuid"
 	"github.com/prebid/prebid-server/errortypes"
 
@@ -40,7 +41,9 @@ func NewVideoEndpoint(ex exchange.Exchange, validator openrtb_ext.BidderParamVal
 	}
 	defRequest := defReqJSON != nil && len(defReqJSON) > 0
 
-	return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap, cache}).VideoAuctionEndpoint), nil
+	videoEndpointRegexp := regexp.MustCompile(`[<>]`)
+
+	return httprouter.Handle((&endpointDeps{ex, validator, requestsById, videoFetcher, categories, cfg, met, pbsAnalytics, disabledBidders, defRequest, defReqJSON, bidderMap, cache, videoEndpointRegexp}).VideoAuctionEndpoint), nil
 }
 
 /*
@@ -89,29 +92,17 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video)
 	}
 	debugLog := exchange.DebugLog{
-		EnableDebug: strings.EqualFold(debugQuery, "true"),
-		CacheType:   prebid_cache_client.TypeXML,
-		TTL:         cacheTTL,
+		Enabled:   strings.EqualFold(debugQuery, "true"),
+		CacheType: prebid_cache_client.TypeXML,
+		TTL:       cacheTTL,
+		Regexp:    deps.debugLogRegexp,
 	}
 
 	defer func() {
 		if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil {
-			debugLog.Data = fmt.Sprintf("<!--\n%s\n\nNo response created\n-->", debugLog.Data)
-			data, err := json.Marshal(debugLog.Data)
-			if err == nil {
-				toCache := []prebid_cache_client.Cacheable{
-					{
-						Type:       debugLog.CacheType,
-						Data:       data,
-						TTLSeconds: debugLog.TTL,
-						Key:        "log_" + debugLog.CacheKey,
-					},
-				}
-				if deps.cache != nil {
-					ctx, cancel := context.WithDeadline(context.Background(), start.Add(time.Duration(100)*time.Millisecond))
-					defer cancel()
-					deps.cache.PutJson(ctx, toCache)
-				}
+			err := putDebugLogError(deps.cache, &debugLog, start)
+			if err != nil {
+				vo.Errors = append(vo.Errors, err)
 			}
 		}
 		deps.metricsEngine.RecordRequest(labels)
@@ -130,12 +121,12 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}
 
 	resolvedRequest := requestJson
-	if debugLog.EnableDebug {
-		debugLog.Data = fmt.Sprintf("Request:\n%s", string(requestJson))
+	if debugLog.Enabled {
+		debugLog.Data.Request = string(requestJson)
 		if headerBytes, err := json.Marshal(r.Header); err == nil {
-			debugLog.Data = fmt.Sprintf("%s\n\nHeaders:\n%s", debugLog.Data, string(headerBytes))
+			debugLog.Data.Headers = string(headerBytes)
 		} else {
-			debugLog.Data = fmt.Sprintf("%s\n\nUnable to marshal headers data\n", debugLog.Data)
+			debugLog.Data.Headers = fmt.Sprintf("Unable to marshal headers data: %s", err.Error())
 		}
 	}
 
@@ -182,7 +173,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	//create full open rtb req from full video request
 	mergeData(videoBidReq, bidReq)
 	// If debug query param is set, force the response to enable test flag
-	if debugLog.EnableDebug {
+	if debugLog.Enabled {
 		bidReq.Test = 1
 	}
 
@@ -281,6 +272,34 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 
 }
 
+func putDebugLogError(cache prebid_cache_client.Client, debugLog *exchange.DebugLog, start time.Time) error {
+	debugLog.Data.Response = "No response created"
+
+	debugLog.BuildCacheString()
+
+	data, err := json.Marshal(debugLog.CacheString)
+	if err != nil {
+		return err
+	}
+
+	toCache := []prebid_cache_client.Cacheable{
+		{
+			Type:       debugLog.CacheType,
+			Data:       data,
+			TTLSeconds: debugLog.TTL,
+			Key:        "log_" + debugLog.CacheKey,
+		},
+	}
+
+	if cache != nil {
+		ctx, cancel := context.WithDeadline(context.Background(), start.Add(time.Duration(100)*time.Millisecond))
+		defer cancel()
+		cache.PutJson(ctx, toCache)
+	}
+
+	return nil
+}
+
 func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) *openrtb_ext.BidRequestVideo {
 	for i := len(podErrors) - 1; i >= 0; i-- {
 		videoReq.PodConfig.Pods = append(videoReq.PodConfig.Pods[:podErrors[i].PodIndex], videoReq.PodConfig.Pods[podErrors[i].PodIndex+1:]...)
@@ -289,7 +308,7 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P
 }
 
 func handleError(labels *pbsmetrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) {
-	if debugLog != nil && debugLog.EnableDebug {
+	if debugLog != nil && debugLog.Enabled {
 		if rawUUID, err := uuid.NewV4(); err == nil {
 			debugLog.CacheKey = rawUUID.String()
 		}
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index 9110b20f..15e57f55 100755
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -44,6 +44,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/lunamedia"
 	"github.com/prebid/prebid-server/adapters/marsmedia"
 	"github.com/prebid/prebid-server/adapters/mgid"
+	"github.com/prebid/prebid-server/adapters/mobilefuse"
 	"github.com/prebid/prebid-server/adapters/nanointeractive"
 	"github.com/prebid/prebid-server/adapters/ninthdecimal"
 	"github.com/prebid/prebid-server/adapters/openx"
@@ -121,6 +122,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
 		openrtb_ext.BidderLunaMedia:       lunamedia.NewLunaMediaBidder(cfg.Adapters[string(openrtb_ext.BidderLunaMedia)].Endpoint),
 		openrtb_ext.BidderMarsmedia:       marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint),
 		openrtb_ext.BidderMgid:            mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint),
+		openrtb_ext.BidderMobileFuse:      mobilefuse.NewMobileFuseBidder(cfg.Adapters[string(openrtb_ext.BidderMobileFuse)].Endpoint),
 		openrtb_ext.BidderNanoInteractive: nanointeractive.NewNanoIneractiveBidder(cfg.Adapters[string(openrtb_ext.BidderNanoInteractive)].Endpoint),
 		openrtb_ext.BidderNinthDecimal:    ninthdecimal.NewNinthDecimalBidder(cfg.Adapters[string(openrtb_ext.BidderNinthDecimal)].Endpoint),
 		openrtb_ext.BidderOrbidder:        orbidder.NewOrbidderBidder(cfg.Adapters[string(openrtb_ext.BidderOrbidder)].Endpoint),
diff --git a/exchange/auction.go b/exchange/auction.go
index 9909b78d..dcb73708 100644
--- a/exchange/auction.go
+++ b/exchange/auction.go
@@ -3,8 +3,10 @@ package exchange
 import (
 	"context"
 	"encoding/json"
+	"encoding/xml"
 	"errors"
 	"fmt"
+	"regexp"
 	"strings"
 
 	uuid "github.com/gofrs/uuid"
@@ -15,6 +17,36 @@ import (
 	"github.com/prebid/prebid-server/prebid_cache_client"
 )
 
+type DebugLog struct {
+	Enabled     bool
+	CacheType   prebid_cache_client.PayloadType
+	Data        DebugData
+	TTL         int64
+	CacheKey    string
+	CacheString string
+	Regexp      *regexp.Regexp
+}
+
+type DebugData struct {
+	Request  string
+	Headers  string
+	Response string
+}
+
+func (d *DebugLog) BuildCacheString() {
+	if d.Regexp != nil {
+		d.Data.Request = fmt.Sprintf(d.Regexp.ReplaceAllString(d.Data.Request, ""))
+		d.Data.Headers = fmt.Sprintf(d.Regexp.ReplaceAllString(d.Data.Headers, ""))
+		d.Data.Response = fmt.Sprintf(d.Regexp.ReplaceAllString(d.Data.Response, ""))
+	}
+
+	d.Data.Request = fmt.Sprintf("<Request>%s</Request>", d.Data.Request)
+	d.Data.Headers = fmt.Sprintf("<Headers>%s</Headers>", d.Data.Headers)
+	d.Data.Response = fmt.Sprintf("<Response>%s</Response>", d.Data.Response)
+
+	d.CacheString = fmt.Sprintf("%s<Log>%s%s%s</Log>", xml.Header, d.Data.Request, d.Data.Headers, d.Data.Response)
+}
+
 func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int) *auction {
 	winningBids := make(map[string]*pbsOrtbBid, numImps)
 	winningBidsByBidder := make(map[string]map[openrtb_ext.BidderName]*pbsOrtbBid, numImps)
@@ -147,9 +179,10 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client,
 		}
 	}
 
-	if debugLog != nil && debugLog.EnableDebug {
+	if debugLog != nil && debugLog.Enabled {
+		debugLog.BuildCacheString()
 		debugLog.CacheKey = hbCacheID
-		if jsonBytes, err := json.Marshal(debugLog.Data); err == nil {
+		if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil {
 			toCache = append(toCache, prebid_cache_client.Cacheable{
 				Type:       debugLog.CacheType,
 				Data:       jsonBytes,
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 48903dd9..6d51b87d 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -27,14 +27,6 @@ import (
 	"github.com/prebid/prebid-server/prebid_cache_client"
 )
 
-type DebugLog struct {
-	EnableDebug bool
-	CacheType   prebid_cache_client.PayloadType
-	Data        string
-	TTL         int64
-	CacheKey    string
-}
-
 // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines.
 type Exchange interface {
 	// HoldAuction executes an OpenRTB v2.5 Auction.
@@ -174,25 +166,33 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 
 		if targData != nil {
 			auc.setRoundedPrices(targData.priceGranularity)
-			if debugLog != nil && debugLog.EnableDebug {
-				bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errs)
-				if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
-					debugLog.Data = fmt.Sprintf("<!--\n%s\n\nResponse:\n%s\n-->", debugLog.Data, string(bidRespExtBytes))
-				} else {
-					errs = append(errs, errors.New("Unable to marshal response ext for debugging"))
-				}
-			}
 
 			if requestExt.Prebid.SupportDeals {
 				dealErrs := applyDealSupport(bidRequest, auc, bidCategory)
 				errs = append(errs, dealErrs...)
 			}
 
+			if debugLog != nil && debugLog.Enabled {
+				bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, bidRequest, resolvedRequest, errs)
+				if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil {
+					debugLog.Data.Response = string(bidRespExtBytes)
+				} else {
+					debugLog.Data.Response = "Unable to marshal response ext for debugging"
+					errs = append(errs, errors.New(debugLog.Data.Response))
+				}
+			}
+
 			cacheErrs := auc.doCache(ctx, e.cache, targData, bidRequest, 60, &e.defaultTTLs, bidCategory, debugLog)
 			if len(cacheErrs) > 0 {
 				errs = append(errs, cacheErrs...)
 			}
 			targData.setTargeting(auc, bidRequest.App != nil, bidCategory)
+
+			// Ensure caching errors are added if the bid response ext has already been created
+			if bidResponseExt != nil && len(cacheErrs) > 0 {
+				bidderCacheErrs := errsToBidderErrors(cacheErrs)
+				bidResponseExt.Errors[openrtb_ext.PrebidExtKey] = append(bidResponseExt.Errors[openrtb_ext.PrebidExtKey], bidderCacheErrs...)
+			}
 		}
 
 	}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index a0a8c6a8..7db59e69 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -62,6 +62,7 @@ const (
 	BidderLunaMedia        BidderName = "lunamedia"
 	BidderMarsmedia        BidderName = "marsmedia"
 	BidderMgid             BidderName = "mgid"
+	BidderMobileFuse       BidderName = "mobilefuse"
 	BidderNanoInteractive  BidderName = "nanointeractive"
 	BidderNinthDecimal     BidderName = "ninthdecimal"
 	BidderOpenx            BidderName = "openx"
@@ -135,6 +136,7 @@ var BidderMap = map[string]BidderName{
 	"lunamedia":         BidderLunaMedia,
 	"marsmedia":         BidderMarsmedia,
 	"mgid":              BidderMgid,
+	"mobilefuse":        BidderMobileFuse,
 	"nanointeractive":   BidderNanoInteractive,
 	"ninthdecimal":      BidderNinthDecimal,
 	"openx":             BidderOpenx,
diff --git a/openrtb_ext/imp_mobilefuse.go b/openrtb_ext/imp_mobilefuse.go
new file mode 100644
index 00000000..ea53c591
--- /dev/null
+++ b/openrtb_ext/imp_mobilefuse.go
@@ -0,0 +1,8 @@
+package openrtb_ext
+
+// ExtImpMobileFuse defines the contract for bidrequest.imp[i].ext.mobilefuse
+type ExtImpMobileFuse struct {
+	PlacementId int    `json:"placement_id"`
+	PublisherId int    `json:"pub_id"`
+	TagidSrc    string `json:"tagid_src"`
+}
diff --git a/router/router.go b/router/router.go
index 68627f93..045c86ef 100644
--- a/router/router.go
+++ b/router/router.go
@@ -6,13 +6,14 @@ import (
 	"database/sql"
 	"encoding/json"
 	"fmt"
-	"github.com/prebid/prebid-server/pbsmetrics"
 	"io/ioutil"
 	"net/http"
 	"path/filepath"
 	"strings"
 	"time"
 
+	"github.com/prebid/prebid-server/pbsmetrics"
+
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adapters/adform"
 	"github.com/prebid/prebid-server/adapters/appnexus"
diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml
new file mode 100644
index 00000000..178e407d
--- /dev/null
+++ b/static/bidder-info/mobilefuse.yaml
@@ -0,0 +1,7 @@
+maintainer:
+  email: prebid@mobilefuse.com
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
diff --git a/static/bidder-params/mobilefuse.json b/static/bidder-params/mobilefuse.json
new file mode 100644
index 00000000..15f17148
--- /dev/null
+++ b/static/bidder-params/mobilefuse.json
@@ -0,0 +1,24 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "MobileFuse Adapter Params",
+    "description": "A schema which validates params accepted by the MobileFuse adapter",
+    "type": "object",
+    "properties": {
+        "placement_id": {
+            "type": "integer",
+            "description": "An ID which identifies this specific inventory placement"
+        },
+        "pub_id": {
+            "type": "integer",
+            "description": "An ID which identifies the publisher selling the inventory."
+        },
+        "tagid_src": {
+            "type": "string",
+            "description": "ext if passing publisher's ids, empty if passing MobileFuse IDs in placement_id field. Defaults to empty"
+        }
+    },
+    "required": [
+        "placement_id",
+        "pub_id"
+    ]
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/mobilefuse/mobilefuse_test.go b/adapters/mobilefuse/mobilefuse_test.go
new file mode 100644
index 00000000..15021a06
--- /dev/null
+++ b/adapters/mobilefuse/mobilefuse_test.go
@@ -0,0 +1,10 @@
+package mobilefuse
+
+import (
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"testing"
+)
+
+func TestJsonSamples(t *testing.T) {
+	adapterstest.RunJSONBidderTest(t, "mobilefusetest", NewMobileFuseBidder("http://mfx-us-east.mobilefuse.com/openrtb?pub_id={{.PublisherID}}"))
+}
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json
new file mode 100644
index 00000000..49fb8819
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-format.json
@@ -0,0 +1,67 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 320,
+                    "h": 480
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 123456,
+                        "pub_id": 1234
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1234",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "tagid": "123456"
+                        }
+                    ]
+                }
+            },
+
+            "mockResponse": {
+                "status": 204,
+                "body": ""
+            }
+        }
+    ],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json
new file mode 100644
index 00000000..61ac1a3e
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/multi-imps.json
@@ -0,0 +1,110 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 123456,
+                        "pub_id": 1234
+                    }
+                }
+            },
+            {
+                "id": "2",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 50
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 234567,
+                        "pub_id": 1234
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1234",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "tagid": "123456"
+                        }
+                    ]
+                }
+            },
+
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "mobilefuse",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 1.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "h": 50,
+                                    "w": 320
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 1.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 50
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/no-bid.json b/adapters/mobilefuse/mobilefusetest/exemplary/no-bid.json
new file mode 100644
index 00000000..cec47a0b
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/no-bid.json
@@ -0,0 +1,55 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1111",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "tagid": "999999"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/optional-params.json b/adapters/mobilefuse/mobilefusetest/exemplary/optional-params.json
new file mode 100644
index 00000000..2f71da8f
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/optional-params.json
@@ -0,0 +1,56 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111,
+                        "tagid_src": "ext"
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1111&tagid_src=ext",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "tagid": "999999"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json b/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json
new file mode 100644
index 00000000..7db7776d
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/simple-banner.json
@@ -0,0 +1,93 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 320,
+                            "h": 50
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 123456,
+                        "pub_id": 1234
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1234",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 320,
+                                        "h": 50
+                                    }
+                                ]
+                            },
+                            "tagid": "123456"
+                        }
+                    ]
+                }
+            },
+
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "seat": "mobilefuse",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 1.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "h": 50,
+                                    "w": 320
+                                }
+                            ]
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 1.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 50
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json b/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json
new file mode 100644
index 00000000..118c82e1
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/simple-video.json
@@ -0,0 +1,96 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 320,
+                    "h": 480
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 200000,
+                        "pub_id": 2000
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=2000",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 320,
+                                "h": 480
+                            },
+                            "tagid": "200000"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "cur": "USD",
+                    "seatbid": [
+                        {
+                            "seat": "mobilefuse",
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "1",
+                                    "price": 2.50,
+                                    "adm": "some-test-ad",
+                                    "crid": "test-crid",
+                                    "w": 320,
+                                    "h": 480
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-bid-id",
+                        "impid": "1",
+                        "price": 2.50,
+                        "adm": "some-test-ad",
+                        "crid": "test-crid",
+                        "w": 320,
+                        "h": 480
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
diff --git a/adapters/mobilefuse/mobilefusetest/params/race/banner.json b/adapters/mobilefuse/mobilefusetest/params/race/banner.json
new file mode 100644
index 00000000..8c8d3f2b
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+    "placement_id": 123456,
+    "pub_id": 1234,
+    "tagid_src": "ext"
+}
diff --git a/adapters/mobilefuse/mobilefusetest/params/race/video.json b/adapters/mobilefuse/mobilefusetest/params/race/video.json
new file mode 100644
index 00000000..8c8d3f2b
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/params/race/video.json
@@ -0,0 +1,5 @@
+{
+    "placement_id": 123456,
+    "pub_id": 1234,
+    "tagid_src": "ext"
+}
diff --git a/adapters/mobilefuse/mobilefusetest/supplemental/bad-ext-bidder.json b/adapters/mobilefuse/mobilefusetest/supplemental/bad-ext-bidder.json
new file mode 100644
index 00000000..61cf115f
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/supplemental/bad-ext-bidder.json
@@ -0,0 +1,36 @@
+{
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpMobileFuse",
+            "comparison": "literal"
+        }
+    ],
+
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": "aaa"
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/supplemental/bad-ext.json b/adapters/mobilefuse/mobilefusetest/supplemental/bad-ext.json
new file mode 100644
index 00000000..f0c23aa2
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/supplemental/bad-ext.json
@@ -0,0 +1,32 @@
+{
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder",
+            "comparison": "literal"
+        }
+    ],
+
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": "aaa"
+            }
+        ]
+    },
+
+    "httpCalls": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/supplemental/bad-status-code.json b/adapters/mobilefuse/mobilefusetest/supplemental/bad-status-code.json
new file mode 100644
index 00000000..5ee94680
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/supplemental/bad-status-code.json
@@ -0,0 +1,62 @@
+{
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Unexpected status code: 400.",
+            "comparison": "literal"
+        }
+    ],
+
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1111",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "tagid": "999999"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 400,
+                "body": ""
+            }
+        }
+    ],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/supplemental/no-imps.json b/adapters/mobilefuse/mobilefusetest/supplemental/no-imps.json
new file mode 100644
index 00000000..eab96d4d
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/supplemental/no-imps.json
@@ -0,0 +1,27 @@
+{
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "No valid imps",
+            "comparison": "literal"
+        }
+    ],
+
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "ext": {
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/mobilefusetest/supplemental/server-error-response.json b/adapters/mobilefuse/mobilefusetest/supplemental/server-error-response.json
new file mode 100644
index 00000000..25c4a0bd
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/supplemental/server-error-response.json
@@ -0,0 +1,62 @@
+{
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Unexpected status code: 500.",
+            "comparison": "literal"
+        }
+    ],
+
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111
+                    }
+                }
+            }
+        ]
+    },
+
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx-us-east.mobilefuse.com/openrtb?pub_id=1111",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "tagid": "999999"
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 500,
+                "body": ""
+            }
+        }
+    ],
+
+    "expectedBidResponses": []
+}
diff --git a/adapters/mobilefuse/params_test.go b/adapters/mobilefuse/params_test.go
new file mode 100644
index 00000000..dbfd8894
--- /dev/null
+++ b/adapters/mobilefuse/params_test.go
@@ -0,0 +1,54 @@
+package mobilefuse
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(test *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+
+	if err != nil {
+		test.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		err := validator.Validate(openrtb_ext.BidderMobileFuse, json.RawMessage(validParam))
+
+		if err != nil {
+			test.Errorf("Schema rejected MobileFuse params: %s\nError: %v", validParam, err)
+		}
+	}
+}
+
+func TestInvalidParams(test *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+
+	if err != nil {
+		test.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		err := validator.Validate(openrtb_ext.BidderMobileFuse, json.RawMessage(invalidParam))
+
+		if err == nil {
+			test.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placement_id":123,"pub_id":456}`,
+	`{"placement_id":123,"pub_id":456,"tagid_src":"ext"}`,
+	`{"placement_id":123, "pub_id":456, "tagid_src":""}`,
+}
+
+var invalidParams = []string{
+	`{"placement_id":123}`,
+	`{"pub_id":456}`,
+	`{"placement_id":"123","pub_id":"456"}`,
+	`{"placement_id":123, "placementId":123}`,
+	`{"tagid_src":"ext"}`,
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 98dfa66d..fdd6b3a4 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -602,7 +602,7 @@ func TestStoredRequests(t *testing.T) {
 	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
 	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
 	theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{})
-	edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap, nil}
+	edep := &endpointDeps{&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, false, []byte{}, openrtb_ext.BidderMap, nil, nil}
 
 	for i, requestData := range testStoredRequests {
 		newRequest, errList := edep.processStoredRequests(context.Background(), json.RawMessage(requestData))
@@ -639,6 +639,7 @@ func TestOversizedRequest(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -672,6 +673,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -810,6 +812,7 @@ func TestDisabledBidder(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 
 	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
@@ -844,6 +847,7 @@ func TestValidateImpExtDisabledBidder(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 	errs := deps.validateImpExt(imp, nil, 0)
 	assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext))
@@ -883,6 +887,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 
 	ui := uint64(1)
@@ -925,6 +930,7 @@ func TestCCPAInvalidValueWarning(t *testing.T) {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 
 	ui := uint64(1)
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 38c9dc3f..5ba34068 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -7,6 +7,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"regexp"
 	"strings"
 	"testing"
 
@@ -989,10 +990,15 @@ func TestHandleErrorDebugLog(t *testing.T) {
 	err1 := errors.New("Error for testing handleError 1")
 	err2 := errors.New("Error for testing handleError 2")
 	debugLog := exchange.DebugLog{
-		EnableDebug: true,
-		CacheType:   prebid_cache_client.TypeXML,
-		Data:        "test debug data",
-		TTL:         int64(3600),
+		Enabled:   true,
+		CacheType: prebid_cache_client.TypeXML,
+		Data: exchange.DebugData{
+			Request:  "test request string",
+			Headers:  "test headers string",
+			Response: "test response string",
+		},
+		TTL:    int64(3600),
+		Regexp: regexp.MustCompile(`[<>]`),
 	}
 	handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog)
 
@@ -1047,6 +1053,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *p
 		[]byte{},
 		openrtb_ext.BidderMap,
 		nil,
+		nil,
 	}
 
 	return edep, theMetrics, mockModule
@@ -1087,6 +1094,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 		[]byte{},
 		openrtb_ext.BidderMap,
 		ex.cache,
+		regexp.MustCompile(`[<>]`),
 	}
 
 	return edep
@@ -1121,7 +1129,7 @@ type mockExchangeVideo struct {
 
 func (m *mockExchangeVideo) HoldAuction(ctx context.Context, bidRequest *openrtb.BidRequest, ids exchange.IdFetcher, labels pbsmetrics.Labels, categoriesFetcher *stored_requests.CategoryFetcher, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
 	m.lastRequest = bidRequest
-	if debugLog != nil && debugLog.EnableDebug {
+	if debugLog != nil && debugLog.Enabled {
 		m.cache.called = true
 	}
 	ext := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"20.00","hb_pb_cat_dur":"20.00_395_30s","hb_size":"1x1", "hb_uuid":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`)
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index d23ff03e..e23c45cb 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -3,8 +3,10 @@ package exchange
 import (
 	"context"
 	"encoding/json"
+	"encoding/xml"
 	"fmt"
 	"io/ioutil"
+	"regexp"
 	"strconv"
 	"strings"
 	"testing"
@@ -40,6 +42,61 @@ func TestMakeVASTNurl(t *testing.T) {
 	assert.Equal(t, expect, vast)
 }
 
+func TestBuildCacheString(t *testing.T) {
+	testCases := []struct {
+		description      string
+		debugLog         DebugLog
+		expectedDebugLog DebugLog
+	}{
+		{
+			description: "DebugLog strings should have tags and be formatted",
+			debugLog: DebugLog{
+				Data: DebugData{
+					Request:  "test request string",
+					Headers:  "test headers string",
+					Response: "test response string",
+				},
+				Regexp: regexp.MustCompile(`[<>]`),
+			},
+			expectedDebugLog: DebugLog{
+				Data: DebugData{
+					Request:  "<Request>test request string</Request>",
+					Headers:  "<Headers>test headers string</Headers>",
+					Response: "<Response>test response string</Response>",
+				},
+				Regexp: regexp.MustCompile(`[<>]`),
+			},
+		},
+		{
+			description: "DebugLog strings should have no < or > characters",
+			debugLog: DebugLog{
+				Data: DebugData{
+					Request:  "<test>test request string</test>",
+					Headers:  "test <headers string",
+					Response: "test <response> string",
+				},
+				Regexp: regexp.MustCompile(`[<>]`),
+			},
+			expectedDebugLog: DebugLog{
+				Data: DebugData{
+					Request:  "<Request>testtest request string/test</Request>",
+					Headers:  "<Headers>test headers string</Headers>",
+					Response: "<Response>test response string</Response>",
+				},
+				Regexp: regexp.MustCompile(`[<>]`),
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		test.expectedDebugLog.CacheString = fmt.Sprintf("%s<Log>%s%s%s</Log>", xml.Header, test.expectedDebugLog.Data.Request, test.expectedDebugLog.Data.Headers, test.expectedDebugLog.Data.Response)
+
+		test.debugLog.BuildCacheString()
+
+		assert.Equal(t, test.expectedDebugLog, test.debugLog, test.description)
+	}
+}
+
 // TestCacheJSON executes tests for all the *.json files in cachetest.
 // customcachekey.json test here verifies custom cache key not used for non-vast video
 func TestCacheJSON(t *testing.T) {
diff --git a/exchange/cachetest/debuglog_disabled.json b/exchange/cachetest/debuglog_disabled.json
index 675488c0..88d6332c 100644
--- a/exchange/cachetest/debuglog_disabled.json
+++ b/exchange/cachetest/debuglog_disabled.json
@@ -1,9 +1,13 @@
 {
     "debugLog": {
-        "EnableDebug": false,
+        "Enabled": false,
         "CacheType": "xml",
         "TTL": 3600,
-        "Data": "test debug data"
+        "Data": {
+            "Request": "test request string",
+            "Headers": "test headers string",
+            "Response": "test response string"
+        }
     },
     "bidRequest": {
         "imp": [{
diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json
index d4486558..670b694f 100644
--- a/exchange/cachetest/debuglog_enabled.json
+++ b/exchange/cachetest/debuglog_enabled.json
@@ -1,9 +1,13 @@
 {    
     "debugLog": {
-        "EnableDebug": true,
+        "Enabled": true,
         "CacheType": "xml",
         "TTL": 3600,
-        "Data": "test debug data"
+        "Data": {
+            "Request": "test request string",
+            "Headers": "test headers string",
+            "Response": "test response string"
+        }
     },
     "bidRequest": {
         "imp": [{
@@ -42,7 +46,7 @@
         }, {
             "Type": "xml",
             "TTLSeconds": 3600,
-            "Data": "test debug data"
+            "Data": "\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cLog\u003e\u003cRequest\u003etest request string\u003c/Request\u003e\u003cHeaders\u003etest headers string\u003c/Headers\u003e\u003cResponse\u003etest response string\u003c/Response\u003e\u003c/Log\u003e"
         }
     ],
     "defaultTTLs": {
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 3c1c2f3b..e9b2127e 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -740,6 +740,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 	debugLog := &DebugLog{}
 	if spec.DebugLog != nil {
 		*debugLog = *spec.DebugLog
+		debugLog.Regexp = regexp.MustCompile(`[<>]`)
 	}
 	bid, err := ex.HoldAuction(context.Background(), &spec.IncomingRequest.OrtbRequest, mockIdFetcher(spec.IncomingRequest.Usersyncs), pbsmetrics.Labels{}, &categoriesFetcher, debugLog)
 	responseTimes := extractResponseTimes(t, filename, bid)
@@ -761,13 +762,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 		}
 	}
 	if spec.DebugLog != nil {
-		if spec.DebugLog.EnableDebug {
-			if len(debugLog.Data) <= len(spec.DebugLog.Data) {
-				t.Errorf("%s: DebugLog was not modified when it should have been", filename)
+		if spec.DebugLog.Enabled {
+			if len(debugLog.Data.Response) == 0 {
+				t.Errorf("%s: DebugLog response was not modified when it should have been", filename)
 			}
 		} else {
-			if !strings.EqualFold(spec.DebugLog.Data, debugLog.Data) {
-				t.Errorf("%s: DebugLog was modified when it shouldn't have been", filename)
+			if len(debugLog.Data.Response) != 0 {
+				t.Errorf("%s: DebugLog response was modified when it shouldn't have been", filename)
 			}
 		}
 	}
diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json
index 0c24c121..3dd04f66 100644
--- a/exchange/exchangetest/debuglog_disabled.json
+++ b/exchange/exchangetest/debuglog_disabled.json
@@ -1,161 +1,230 @@
 {
-    "debugLog": {
-        "EnableDebug": false,
-        "CacheType": "xml",
-        "TTL": 3600,
-        "Data": "test debug data"
-    },
-    "incomingRequest": {
-      "ortbRequest": {
-        "id": "some-request-id",
-        "site": {
-          "page": "test.somepage.com"
-        },
-        "imp": [
-          {
-            "id": "my-imp-id",
-            "video": {
-              "mimes": ["video/mp4"]
-            },
-            "ext": {
-              "appnexus": {
-                "placementId": 1
-              }
-            }
+  "debugLog": {
+    "Enabled": false,
+    "CacheType": "xml",
+    "TTL": 3600,
+    "Data": {
+      "Request": "test request string",
+      "Headers": "test headers string",
+      "Response": ""
+    }
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
           },
-          {
-            "id": "imp-id-2",
-            "video": {
-              "mimes": ["video/mp4"]
-            },
-            "ext": {
-              "appnexus": {
-                "placementId": 1
-              }
+          "ext": {
+            "appnexus": {
+              "placementId": 1
             }
           }
-        ],
-        "ext": {
-          "prebid": {
-            "targeting": {
-              "includebrandcategory": {
-                "primaryadserver": 1,
-                "publisher":"",
-                "withcategory": true
-              }
+        },
+        {
+          "id": "imp-id-2",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "appnexus": {
+              "placementId": 1
             }
           }
         }
-      },
-      "usersyncs": {
-        "appnexus": "123"
-      }
-    },
-    "outgoingRequests": {
-      "appnexus": {
-        "mockResponse": {
-          "pbsSeatBid": {
-            "pbsBids": [
-              {
-                "ortbBid": {
-                  "id": "apn-bid",
-                  "impid": "my-imp-id",
-                  "price": 0.3,
-                  "w": 200,
-                  "h": 250,
-                  "crid": "creative-1",
-                  "cat": ["IAB1-1"]
-                },
-                "bidType": "video",
-                "bidVideo": {
-                  "duration": 30,
-                  "PrimaryCategory": ""
-                }
-              },
-              {
-                "ortbBid": {
-                  "id": "apn-other-bid",
-                  "impid": "imp-id-2",
-                  "price": 0.6,
-                  "w": 300,
-                  "h": 500,
-                  "crid": "creative-3",
-                  "cat": ["IAB1-2"]
-                },
-                "bidType": "video"
-              }
-            ]
+      ],
+      "test": 1,
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includebrandcategory": {
+              "primaryadserver": 1,
+              "publisher": "",
+              "withcategory": true
+            }
           }
         }
       }
     },
-    "response": {
-      "bids": {
-        "id": "some-request-id",
-        "seatbid": [
-          {
-            "seat": "appnexus",
-            "bid": [
-              {
+    "usersyncs": {
+      "appnexus": "123"
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBid": {
+          "pbsBids": [
+            {
+              "ortbBid": {
                 "id": "apn-bid",
                 "impid": "my-imp-id",
                 "price": 0.3,
                 "w": 200,
                 "h": 250,
                 "crid": "creative-1",
-                "cat": ["IAB1-1"],
-                "ext": {
-                  "prebid": {
-                    "type": "video",
-                    "targeting": {
-                       "hb_bidder": "appnexus",
-                       "hb_bidder_appnexus": "appnexus",
-                       "hb_cache_host": "www.pbcserver.com",
-                       "hb_cache_host_appnex": "www.pbcserver.com",
-                       "hb_cache_path": "/pbcache/endpoint",
-                       "hb_cache_path_appnex": "/pbcache/endpoint",
-                       "hb_pb": "0.20",
-                       "hb_pb_appnexus": "0.20",
-                       "hb_pb_cat_dur": "0.20_VideoGames_0s",
-                       "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
-                       "hb_size": "200x250",
-                       "hb_size_appnexus": "200x250"
-                     }
-                  }
-                }
+                "cat": [
+                  "IAB1-1"
+                ]
               },
-              {
-                "cat": ["IAB1-2"],
-                "crid": "creative-3",
-                "ext": {
-                  "prebid": {
-                    "targeting": {
-                        "hb_bidder": "appnexus",
-                        "hb_bidder_appnexus": "appnexus",
-                        "hb_cache_host": "www.pbcserver.com",
-                        "hb_cache_host_appnex": "www.pbcserver.com",
-                        "hb_cache_path": "/pbcache/endpoint",
-                        "hb_cache_path_appnex": "/pbcache/endpoint",
-                        "hb_pb": "0.50",
-                        "hb_pb_appnexus": "0.50",
-                        "hb_pb_cat_dur": "0.50_HomeDecor_0s",
-                        "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s",
-                        "hb_size": "300x500",
-                        "hb_size_appnexus": "300x500"
-                      },
-                    "type": "video"
-                  }
-                },
-                "h": 500,
+              "bidType": "video",
+              "bidVideo": {
+                "duration": 30,
+                "PrimaryCategory": ""
+              }
+            },
+            {
+              "ortbBid": {
                 "id": "apn-other-bid",
                 "impid": "imp-id-2",
                 "price": 0.6,
-                "w": 300
+                "w": 300,
+                "h": 500,
+                "crid": "creative-3",
+                "cat": [
+                  "IAB1-2"
+                ]
+              },
+              "bidType": "video"
+            }
+          ]
+        }
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid",
+              "impid": "my-imp-id",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "prebid": {
+                  "type": "video",
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.20",
+                    "hb_pb_appnexus": "0.20",
+                    "hb_pb_cat_dur": "0.20_VideoGames_0s",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                    "hb_size": "200x250",
+                    "hb_size_appnexus": "200x250"
+                  }
+                }
               }
-            ]
+            },
+            {
+              "cat": [
+                "IAB1-2"
+              ],
+              "crid": "creative-3",
+              "ext": {
+                "prebid": {
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.50",
+                    "hb_pb_appnexus": "0.50",
+                    "hb_pb_cat_dur": "0.50_HomeDecor_0s",
+                    "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s",
+                    "hb_size": "300x500",
+                    "hb_size_appnexus": "300x500"
+                  },
+                  "type": "video"
+                }
+              },
+              "h": 500,
+              "id": "apn-other-bid",
+              "impid": "imp-id-2",
+              "price": 0.6,
+              "w": 300
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "httpcalls": {
+          "appnexus": null
+        },
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            },
+            {
+              "id": "imp-id-2",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "test": 1,
+          "ext": {
+            "prebid": {
+              "targeting": {
+                "includebrandcategory": {
+                  "primaryadserver": 1,
+                  "publisher": "",
+                  "withcategory": true
+                }
+              }
+            }
           }
-        ]
+        }
       }
     }
   }
-  
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json
index 281bf3a1..a70013b4 100644
--- a/exchange/exchangetest/debuglog_enabled.json
+++ b/exchange/exchangetest/debuglog_enabled.json
@@ -1,161 +1,230 @@
 {
-    "debugLog": {
-        "EnableDebug": true,
-        "CacheType": "xml",
-        "TTL": 3600,
-        "Data": "test debug data"
-    },
-    "incomingRequest": {
-      "ortbRequest": {
-        "id": "some-request-id",
-        "site": {
-          "page": "test.somepage.com"
-        },
-        "imp": [
-          {
-            "id": "my-imp-id",
-            "video": {
-              "mimes": ["video/mp4"]
-            },
-            "ext": {
-              "appnexus": {
-                "placementId": 1
-              }
-            }
+  "debugLog": {
+    "Enabled": true,
+    "CacheType": "xml",
+    "TTL": 3600,
+    "Data": {
+      "Request": "test request string",
+      "Headers": "test headers string",
+      "Response": ""
+    }
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
           },
-          {
-            "id": "imp-id-2",
-            "video": {
-              "mimes": ["video/mp4"]
-            },
-            "ext": {
-              "appnexus": {
-                "placementId": 1
-              }
+          "ext": {
+            "appnexus": {
+              "placementId": 1
             }
           }
-        ],
-        "ext": {
-          "prebid": {
-            "targeting": {
-              "includebrandcategory": {
-                "primaryadserver": 1,
-                "publisher":"",
-                "withcategory": true
-              }
+        },
+        {
+          "id": "imp-id-2",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "appnexus": {
+              "placementId": 1
             }
           }
         }
-      },
-      "usersyncs": {
-        "appnexus": "123"
-      }
-    },
-    "outgoingRequests": {
-      "appnexus": {
-        "mockResponse": {
-          "pbsSeatBid": {
-            "pbsBids": [
-              {
-                "ortbBid": {
-                  "id": "apn-bid",
-                  "impid": "my-imp-id",
-                  "price": 0.3,
-                  "w": 200,
-                  "h": 250,
-                  "crid": "creative-1",
-                  "cat": ["IAB1-1"]
-                },
-                "bidType": "video",
-                "bidVideo": {
-                  "duration": 30,
-                  "PrimaryCategory": ""
-                }
-              },
-              {
-                "ortbBid": {
-                  "id": "apn-other-bid",
-                  "impid": "imp-id-2",
-                  "price": 0.6,
-                  "w": 300,
-                  "h": 500,
-                  "crid": "creative-3",
-                  "cat": ["IAB1-2"]
-                },
-                "bidType": "video"
-              }
-            ]
+      ],
+      "test": 1,
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includebrandcategory": {
+              "primaryadserver": 1,
+              "publisher": "",
+              "withcategory": true
+            }
           }
         }
       }
     },
-    "response": {
-      "bids": {
-        "id": "some-request-id",
-        "seatbid": [
-          {
-            "seat": "appnexus",
-            "bid": [
-              {
+    "usersyncs": {
+      "appnexus": "123"
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBid": {
+          "pbsBids": [
+            {
+              "ortbBid": {
                 "id": "apn-bid",
                 "impid": "my-imp-id",
                 "price": 0.3,
                 "w": 200,
                 "h": 250,
                 "crid": "creative-1",
-                "cat": ["IAB1-1"],
-                "ext": {
-                  "prebid": {
-                    "type": "video",
-                    "targeting": {
-                       "hb_bidder": "appnexus",
-                       "hb_bidder_appnexus": "appnexus",
-                       "hb_cache_host": "www.pbcserver.com",
-                       "hb_cache_host_appnex": "www.pbcserver.com",
-                       "hb_cache_path": "/pbcache/endpoint",
-                       "hb_cache_path_appnex": "/pbcache/endpoint",
-                       "hb_pb": "0.20",
-                       "hb_pb_appnexus": "0.20",
-                       "hb_pb_cat_dur": "0.20_VideoGames_0s",
-                       "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
-                       "hb_size": "200x250",
-                       "hb_size_appnexus": "200x250"
-                     }
-                  }
-                }
+                "cat": [
+                  "IAB1-1"
+                ]
               },
-              {
-                "cat": ["IAB1-2"],
-                "crid": "creative-3",
-                "ext": {
-                  "prebid": {
-                    "targeting": {
-                        "hb_bidder": "appnexus",
-                        "hb_bidder_appnexus": "appnexus",
-                        "hb_cache_host": "www.pbcserver.com",
-                        "hb_cache_host_appnex": "www.pbcserver.com",
-                        "hb_cache_path": "/pbcache/endpoint",
-                        "hb_cache_path_appnex": "/pbcache/endpoint",
-                        "hb_pb": "0.50",
-                        "hb_pb_appnexus": "0.50",
-                        "hb_pb_cat_dur": "0.50_HomeDecor_0s",
-                        "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s",
-                        "hb_size": "300x500",
-                        "hb_size_appnexus": "300x500"
-                      },
-                    "type": "video"
-                  }
-                },
-                "h": 500,
+              "bidType": "video",
+              "bidVideo": {
+                "duration": 30,
+                "PrimaryCategory": ""
+              }
+            },
+            {
+              "ortbBid": {
                 "id": "apn-other-bid",
                 "impid": "imp-id-2",
                 "price": 0.6,
-                "w": 300
+                "w": 300,
+                "h": 500,
+                "crid": "creative-3",
+                "cat": [
+                  "IAB1-2"
+                ]
+              },
+              "bidType": "video"
+            }
+          ]
+        }
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid",
+              "impid": "my-imp-id",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "prebid": {
+                  "type": "video",
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.20",
+                    "hb_pb_appnexus": "0.20",
+                    "hb_pb_cat_dur": "0.20_VideoGames_0s",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                    "hb_size": "200x250",
+                    "hb_size_appnexus": "200x250"
+                  }
+                }
               }
-            ]
+            },
+            {
+              "cat": [
+                "IAB1-2"
+              ],
+              "crid": "creative-3",
+              "ext": {
+                "prebid": {
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.50",
+                    "hb_pb_appnexus": "0.50",
+                    "hb_pb_cat_dur": "0.50_HomeDecor_0s",
+                    "hb_pb_cat_dur_appnex": "0.50_HomeDecor_0s",
+                    "hb_size": "300x500",
+                    "hb_size_appnexus": "300x500"
+                  },
+                  "type": "video"
+                }
+              },
+              "h": 500,
+              "id": "apn-other-bid",
+              "impid": "imp-id-2",
+              "price": 0.6,
+              "w": 300
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "httpcalls": {
+          "appnexus": null
+        },
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            },
+            {
+              "id": "imp-id-2",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "test": 1,
+          "ext": {
+            "prebid": {
+              "targeting": {
+                "includebrandcategory": {
+                  "primaryadserver": 1,
+                  "publisher": "",
+                  "withcategory": true
+                }
+              }
+            }
           }
-        ]
+        }
       }
     }
   }
-  
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 49ce85d0..f448da50 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -80,12 +80,13 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderAdgeneration: true,
 		openrtb_ext.BidderAdoppler:     true,
 		openrtb_ext.BidderApplogy:      true,
-		openrtb_ext.BidderTappx:        true,
+		openrtb_ext.BidderKidoz:        true,
 		openrtb_ext.BidderKubient:      true,
+		openrtb_ext.BidderMobileFuse:   true,
+		openrtb_ext.BidderOrbidder:     true,
 		openrtb_ext.BidderPubnative:    true,
-		openrtb_ext.BidderKidoz:        true,
+		openrtb_ext.BidderTappx:        true,
 		openrtb_ext.BidderYeahmobi:     true,
-		openrtb_ext.BidderOrbidder:     true,
 	}
 
 	for bidder, config := range cfg.Adapters {