Repository: prebid/prebid-server
Pull request #1463 (2020-08-27 08:39:12)

Smaato adapter: support for video mediaType
### Description

This PR allows the Smaato adapter to send video bid requests to the Smaato bidder endpoint.


Patch:
diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go
index 243f1b80..ff7451f1 100644
--- a/adapters/consumable/consumable.go
+++ b/adapters/consumable/consumable.go
@@ -69,6 +69,8 @@ type decision struct {
 	CreativeID    string     `json:"creativeId,omitempty"`
 	Contents      []contents `json:"contents"`
 	ImpressionUrl *string    `json:"impressionUrl,omitempty"`
+	Width         uint64     `json:"width,omitempty"`  // Consumable extension, not defined by Adzerk
+	Height        uint64     `json:"height,omitempty"` // Consumable extension, not defined by Adzerk
 }
 
 type contents struct {
@@ -241,23 +243,13 @@ func (a *ConsumableAdapter) MakeBids(
 	for impID, decision := range serverResponse.Decisions {
 
 		if decision.Pricing != nil && decision.Pricing.ClearPrice != nil {
-
-			imp := getImp(impID, internalRequest.Imp)
-			if imp == nil {
-				errors = append(errors, &errortypes.BadServerResponse{
-					Message: fmt.Sprintf(
-						"ignoring bid id=%s, request doesn't contain any impression with id=%s", internalRequest.ID, impID),
-				})
-				continue
-			}
-
 			bid := openrtb.Bid{}
 			bid.ID = internalRequest.ID
 			bid.ImpID = impID
 			bid.Price = *decision.Pricing.ClearPrice
 			bid.AdM = retrieveAd(decision)
-			bid.W = imp.Banner.Format[0].W // TODO: Review to check if this is correct behaviour
-			bid.H = imp.Banner.Format[0].H
+			bid.W = decision.Width
+			bid.H = decision.Height
 			bid.CrID = strconv.FormatInt(decision.AdID, 10)
 			bid.Exp = 30 // TODO: Check this is intention of TTL
 
@@ -279,15 +271,6 @@ func (a *ConsumableAdapter) MakeBids(
 	return bidderResponse, errors
 }
 
-func getImp(impId string, imps []openrtb.Imp) *openrtb.Imp {
-	for _, imp := range imps {
-		if imp.ID == impId {
-			return &imp
-		}
-	}
-	return nil
-}
-
 func extractExtensions(impression openrtb.Imp) (*adapters.ExtImpBidder, *openrtb_ext.ExtImpConsumable, []error) {
 	var bidderExt adapters.ExtImpBidder
 	if err := json.Unmarshal(impression.Ext, &bidderExt); err != nil {
diff --git a/adapters/gamma/gamma.go b/adapters/gamma/gamma.go
index c011954f..37567235 100644
--- a/adapters/gamma/gamma.go
+++ b/adapters/gamma/gamma.go
@@ -17,6 +17,27 @@ type GammaAdapter struct {
 	URI string
 }
 
+type gammaBid struct {
+	openrtb.Bid        //base
+	VastXML     string `json:"vastXml,omitempty"`
+	VastURL     string `json:"vastUrl,omitempty"`
+}
+
+type gammaSeatBid struct {
+	Bid   []gammaBid      `json:"bid"`
+	Group int8            `json:"group,omitempty"`
+	Ext   json.RawMessage `json:"ext,omitempty"`
+}
+type gammaBidResponse struct {
+	ID         string                   `json:"id"`
+	SeatBid    []gammaSeatBid           `json:"seatbid,omitempty"`
+	BidID      string                   `json:"bidid,omitempty"`
+	Cur        string                   `json:"cur,omitempty"`
+	CustomData string                   `json:"customdata,omitempty"`
+	NBR        *openrtb.NoBidReasonCode `json:"nbr,omitempty"`
+	Ext        json.RawMessage          `json:"ext,omitempty"`
+}
+
 func checkParams(gammaExt openrtb_ext.ExtImpGamma) error {
 	if gammaExt.PartnerID == "" {
 		return &errortypes.BadInput{
@@ -180,6 +201,28 @@ func (a *GammaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte
 	return adapterRequests, errs
 }
 
+func convertBid(gBid gammaBid, mediaType openrtb_ext.BidType) *openrtb.Bid {
+	var bid openrtb.Bid
+	bid = gBid.Bid
+
+	if mediaType == openrtb_ext.BidTypeVideo {
+		//Return inline VAST XML Document (Section 6.4.2)
+		if len(gBid.VastXML) > 0 {
+			if len(gBid.VastURL) > 0 {
+				bid.NURL = gBid.VastURL
+			}
+			bid.AdM = gBid.VastXML
+		} else {
+			return nil
+		}
+	} else {
+		if len(gBid.Bid.AdM) == 0 {
+			return nil
+		}
+	}
+	return &bid
+}
+
 func (a *GammaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
 	if response.StatusCode == http.StatusNoContent {
 		return nil, nil
@@ -197,24 +240,38 @@ func (a *GammaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq
 		}}
 	}
 
-	var bidResp openrtb.BidResponse
-	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+	var gammaResp gammaBidResponse
+	if err := json.Unmarshal(response.Body, &gammaResp); err != nil {
 		return nil, []error{&errortypes.BadServerResponse{
 			Message: fmt.Sprintf("bad server response: %d. ", err),
 		}}
 	}
 
-	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
-	for _, sb := range bidResp.SeatBid {
+	//(Section 7.1 No-Bid Signaling)
+	if len(gammaResp.SeatBid) == 0 {
+		return nil, nil
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(gammaResp.SeatBid[0].Bid))
+	errs := make([]error, 0, len(gammaResp.SeatBid[0].Bid))
+	for _, sb := range gammaResp.SeatBid {
 		for i := range sb.Bid {
-			bid := sb.Bid[i]
-			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
-				Bid:     &bid,
-				BidType: getMediaTypeForImp(bidResp.ID, internalRequest.Imp),
-			})
+			mediaType := getMediaTypeForImp(gammaResp.ID, internalRequest.Imp)
+			bid := convertBid(sb.Bid[i], mediaType)
+			if bid != nil {
+				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+					Bid:     bid,
+					BidType: mediaType,
+				})
+			} else {
+				err := &errortypes.BadServerResponse{
+					Message: fmt.Sprintf("Missing Ad Markup. Run with request.debug = 1 for more info"),
+				}
+				errs = append(errs, err)
+			}
 		}
 	}
-	return bidResponse, nil
+	return bidResponse, errs
 }
 
 //Adding header fields to request header
diff --git a/adapters/info.go b/adapters/info.go
index 732ae855..982827c9 100644
--- a/adapters/info.go
+++ b/adapters/info.go
@@ -37,6 +37,7 @@ type InfoAwareBidder struct {
 
 func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) {
 	var allowedMediaTypes parsedSupports
+
 	if request.Site != nil {
 		if !i.info.site.enabled {
 			return nil, []error{BadInput("this bidder does not support site requests")}
@@ -56,7 +57,14 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *Ext
 	// To avoid allocating new arrays and copying in the normal case, we'll make one pass to
 	// see if any imps need to be removed, and another to do the removing if necessary.
 	numToFilter, errs := i.pruneImps(request.Imp, allowedMediaTypes)
+
+	// If all imps in bid request come with unsupported media types, exit
+	if numToFilter == len(request.Imp) {
+		return nil, append(errs, BadInput("Bid request didn't contain media types supported by the bidder"))
+	}
+
 	if numToFilter != 0 {
+		// Filter out imps with unsupported media types
 		filteredImps, newErrs := i.filterImps(request.Imp, numToFilter)
 		request.Imp = filteredImps
 		errs = append(errs, newErrs...)
diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go
index 06678d77..b48851bb 100644
--- a/adapters/smaato/smaato.go
+++ b/adapters/smaato/smaato.go
@@ -20,6 +20,7 @@ type adMarkupType string
 const (
 	smtAdTypeImg       adMarkupType = "Img"
 	smtAdTypeRichmedia adMarkupType = "Richmedia"
+	smtAdTypeVideo     adMarkupType = "Video"
 )
 
 // SmaatoAdapter describes a Smaato prebid server adapter.
@@ -63,7 +64,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt
 	}
 
 	// Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId
-	publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId")
+	publisherID, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId")
 	if err != nil {
 		errs = append(errs, err)
 		return nil, errs
@@ -80,7 +81,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt
 	}
 	if request.Site != nil {
 		siteCopy := *request.Site
-		siteCopy.Publisher = &openrtb.Publisher{ID: publisherId}
+		siteCopy.Publisher = &openrtb.Publisher{ID: publisherID}
 
 		if request.Site.Ext != nil {
 			var siteExt siteExt
@@ -178,16 +179,25 @@ func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe
 		for i := 0; i < len(sb.Bid); i++ {
 			bid := sb.Bid[i]
 
+			markupType, markupTypeErr := getAdMarkupType(response, bid.AdM)
+			if markupTypeErr != nil {
+				return nil, []error{markupTypeErr}
+			}
+
 			var markupError error
-			bid.AdM, markupError = renderAdMarkup(getAdMarkupType(response, bid.AdM), bid.AdM)
+			bid.AdM, markupError = renderAdMarkup(markupType, bid.AdM)
 			if markupError != nil {
-				fmt.Println(markupError)
-				continue // no bid when broken ad markup
+				return nil, []error{markupError}
+			}
+
+			bidType, bidTypeErr := markupTypeToBidType(markupType)
+			if bidTypeErr != nil {
+				return nil, []error{bidTypeErr}
 			}
 
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 				Bid:     &bid,
-				BidType: openrtb_ext.BidTypeBanner,
+				BidType: bidType,
 			})
 		}
 	}
@@ -202,23 +212,41 @@ func renderAdMarkup(adMarkupType adMarkupType, adMarkup string) (string, error)
 		adm, markupError = extractAdmImage(adMarkup)
 	case smtAdTypeRichmedia:
 		adm, markupError = extractAdmRichMedia(adMarkup)
+	case smtAdTypeVideo:
+		adm, markupError = adMarkup, nil
 	default:
 		return "", fmt.Errorf("Unknown markup type %s", adMarkupType)
 	}
 	return adm, markupError
 }
 
-func getAdMarkupType(response *adapters.ResponseData, adMarkup string) adMarkupType {
+func markupTypeToBidType(markupType adMarkupType) (openrtb_ext.BidType, error) {
+	switch markupType {
+	case smtAdTypeImg:
+		return openrtb_ext.BidTypeBanner, nil
+	case smtAdTypeRichmedia:
+		return openrtb_ext.BidTypeBanner, nil
+	case smtAdTypeVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	default:
+		return "", fmt.Errorf("Invalid markupType %s", markupType)
+	}
+}
+
+func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkupType, error) {
 	if admType := adMarkupType(response.Headers.Get("X-SMT-ADTYPE")); admType != "" {
-		return admType
+		return admType, nil
 	}
 	if strings.HasPrefix(adMarkup, `{"image":`) {
-		return smtAdTypeImg
+		return smtAdTypeImg, nil
 	}
 	if strings.HasPrefix(adMarkup, `{"richmedia":`) {
-		return smtAdTypeRichmedia
+		return smtAdTypeRichmedia, nil
+	}
+	if strings.HasPrefix(adMarkup, `<?xml`) {
+		return smtAdTypeVideo, nil
 	}
-	return ""
+	return "", fmt.Errorf("Invalid ad markup %s", adMarkup)
 }
 
 func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) {
@@ -255,7 +283,14 @@ func parseImpressionObject(imp *openrtb.Imp) error {
 		imp.Ext = nil
 		return nil
 	}
-	return fmt.Errorf("invalid MediaType. SMAATO only supports Banner. Ignoring ImpID=%s", imp.ID)
+
+	if imp.Video != nil {
+		imp.TagID = adSpaceID
+		imp.Ext = nil
+		return nil
+	}
+
+	return fmt.Errorf("invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=%s", imp.ID)
 }
 
 func extractUserExtAttributes(userExt userExt, userCopy *openrtb.User) {
diff --git a/analytics/pubstack/eventchannel/eventchannel.go b/analytics/pubstack/eventchannel/eventchannel.go
index b8dc4dd8..d9c2bc41 100644
--- a/analytics/pubstack/eventchannel/eventchannel.go
+++ b/analytics/pubstack/eventchannel/eventchannel.go
@@ -93,10 +93,13 @@ func (c *EventChannel) flush() {
 		return
 	}
 
+	// reset buffers and writers
+	defer c.reset()
+
 	// finish writing gzip header
-	err := c.gz.Flush()
+	err := c.gz.Close()
 	if err != nil {
-		glog.Warning("[pubstack] fail to flush gzipped buffer")
+		glog.Warning("[pubstack] fail to close gzipped buffer")
 		return
 	}
 
@@ -108,9 +111,6 @@ func (c *EventChannel) flush() {
 		return
 	}
 
-	// reset buffers and writers
-	c.reset()
-
 	// send events (async)
 	go c.send(payload)
 }
diff --git a/config/config.go b/config/config.go
index e3b7d8eb..c2e7dfd8 100755
--- a/config/config.go
+++ b/config/config.go
@@ -136,6 +136,10 @@ func (data *ExternalCache) validate(errs configErrors) configErrors {
 		return errs
 	}
 
+	if data.Scheme != "" && data.Scheme != "http" && data.Scheme != "https" {
+		return append(errs, errors.New("External cache Scheme must be http or https if specified"))
+	}
+
 	// Either host or path or both not empty, validate.
 	if data.Host == "" && data.Path != "" || data.Host != "" && data.Path == "" {
 		return append(errs, errors.New("External cache Host and Path must both be specified"))
@@ -486,13 +490,14 @@ type DataCache struct {
 	TTLSeconds int    `mapstructure:"ttl_seconds"`
 }
 
-// Data type where we store the external cache URL elements. This is completely unrelated to type Cache struct defined afterwards, because
-// the latter is used for internal cache URL while the following contains information of the external cache URL.
+// ExternalCache configures the externally accessible cache url.
 type ExternalCache struct {
-	Host string `mapstructure:"host"`
-	Path string `mapstructure:"path"`
+	Scheme string `mapstructure:"scheme"`
+	Host   string `mapstructure:"host"`
+	Path   string `mapstructure:"path"`
 }
 
+// Cache configures the url used internally by Prebid Server to communicate with Prebid Cache.
 type Cache struct {
 	Scheme string `mapstructure:"scheme"`
 	Host   string `mapstructure:"host"`
@@ -734,6 +739,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("cache.default_ttl_seconds.video", 0)
 	v.SetDefault("cache.default_ttl_seconds.native", 0)
 	v.SetDefault("cache.default_ttl_seconds.audio", 0)
+	v.SetDefault("external_cache.scheme", "")
 	v.SetDefault("external_cache.host", "")
 	v.SetDefault("external_cache.path", "")
 	v.SetDefault("recaptcha_secret", "")
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 1fbdfe8e..59e87669 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"math/rand"
 	"net/http"
+	"net/url"
 	"runtime/debug"
 	"sort"
 	"strconv"
@@ -107,7 +108,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque
 	shouldCacheBids, shouldCacheVAST := getExtCacheInfo(requestExt)
 	targData := getExtTargetData(requestExt, shouldCacheBids, shouldCacheVAST)
 	if targData != nil {
-		targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
+		_, targData.cacheHost, targData.cachePath = e.cache.GetExtCacheData()
 	}
 
 	debugInfo := getDebugInfo(bidRequest, requestExt)
@@ -814,24 +815,50 @@ func (e *exchange) makeBid(Bids []*pbsOrtbBid, adapter openrtb_ext.BidderName, a
 
 // If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`,
 // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL
-func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auc *auction) (openrtb_ext.ExtBidPrebidCacheBids, bool) {
-	var cacheInfo openrtb_ext.ExtBidPrebidCacheBids
-	var cacheUUID string
-	var found bool = false
-
-	if auc != nil {
-		var extCacheHost, extCachePath string
-		if cacheUUID, found = auc.cacheIds[bid.bid]; found {
-			cacheInfo.CacheId = cacheUUID
-			extCacheHost, extCachePath = e.cache.GetExtCacheData()
-			cacheInfo.Url = extCacheHost + extCachePath + "?uuid=" + cacheUUID
-		} else if cacheUUID, found = auc.vastCacheIds[bid.bid]; found {
-			cacheInfo.CacheId = cacheUUID
-			extCacheHost, extCachePath = e.cache.GetExtCacheData()
-			cacheInfo.Url = extCacheHost + extCachePath + "?uuid=" + cacheUUID
+func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) {
+	uuid, found := findCacheID(bid, auction)
+
+	if found {
+		cacheInfo.CacheId = uuid
+		cacheInfo.Url = buildCacheURL(e.cache, uuid)
+	}
+
+	return
+}
+
+func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) {
+	if bid != nil && bid.bid != nil && auction != nil {
+		if id, found := auction.cacheIds[bid.bid]; found {
+			return id, true
+		}
+
+		if id, found := auction.vastCacheIds[bid.bid]; found {
+			return id, true
 		}
 	}
-	return cacheInfo, found
+
+	return "", false
+}
+
+func buildCacheURL(cache prebid_cache_client.Client, uuid string) string {
+	scheme, host, path := cache.GetExtCacheData()
+
+	if host == "" || path == "" {
+		return ""
+	}
+
+	query := url.Values{"uuid": []string{uuid}}
+	cacheURL := url.URL{
+		Scheme:   scheme,
+		Host:     host,
+		Path:     path,
+		RawQuery: query.Encode(),
+	}
+	cacheURL.Query()
+
+	// URLs without a scheme will begin with //, in which case we
+	// want to trim it off to keep compatbile with current behavior.
+	return strings.TrimPrefix(cacheURL.String(), "//")
 }
 
 func listBiddersWithRequests(cleanRequests map[openrtb_ext.BidderName]*openrtb.BidRequest) []openrtb_ext.BidderName {
diff --git a/prebid_cache_client/client.go b/prebid_cache_client/client.go
index a5730ce7..1ad78830 100644
--- a/prebid_cache_client/client.go
+++ b/prebid_cache_client/client.go
@@ -29,8 +29,8 @@ type Client interface {
 	// logging any relevant errors to the app logs
 	PutJson(ctx context.Context, values []Cacheable) ([]string, []error)
 
-	// Serves the purpose of a getter that returns the host and the cache of the prebid-server URL
-	GetExtCacheData() (string, string)
+	// GetExtCacheData gets the scheme, host, and path of the externally accessible cache url.
+	GetExtCacheData() (scheme string, host string, path string)
 }
 
 type PayloadType string
@@ -49,23 +49,25 @@ type Cacheable struct {
 
 func NewClient(httpClient *http.Client, conf *config.Cache, extCache *config.ExternalCache, metrics pbsmetrics.MetricsEngine) Client {
 	return &clientImpl{
-		httpClient:        httpClient,
-		putUrl:            conf.GetBaseURL() + "/cache",
-		externalCacheHost: extCache.Host,
-		externalCachePath: extCache.Path,
-		metrics:           metrics,
+		httpClient:          httpClient,
+		putUrl:              conf.GetBaseURL() + "/cache",
+		externalCacheScheme: extCache.Scheme,
+		externalCacheHost:   extCache.Host,
+		externalCachePath:   extCache.Path,
+		metrics:             metrics,
 	}
 }
 
 type clientImpl struct {
-	httpClient        *http.Client
-	putUrl            string
-	externalCacheHost string
-	externalCachePath string
-	metrics           pbsmetrics.MetricsEngine
+	httpClient          *http.Client
+	putUrl              string
+	externalCacheScheme string
+	externalCacheHost   string
+	externalCachePath   string
+	metrics             pbsmetrics.MetricsEngine
 }
 
-func (c *clientImpl) GetExtCacheData() (string, string) {
+func (c *clientImpl) GetExtCacheData() (string, string, string) {
 	path := c.externalCachePath
 	if path == "/" {
 		// Only the slash for the path, remove it to empty
@@ -75,7 +77,7 @@ func (c *clientImpl) GetExtCacheData() (string, string) {
 		path = "/" + path
 	}
 
-	return c.externalCacheHost, path
+	return c.externalCacheScheme, c.externalCacheHost, path
 }
 
 func (c *clientImpl) PutJson(ctx context.Context, values []Cacheable) (uuids []string, errs []error) {
diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml
index 662603fe..db3e61e5 100644
--- a/static/bidder-info/smaato.yaml
+++ b/static/bidder-info/smaato.yaml
@@ -4,6 +4,8 @@ capabilities:
   app:
     mediaTypes:
       - banner
+      - video
   site:
     mediaTypes:
-      - banner
\ No newline at end of file
+      - banner
+      - video


Test patch:
diff --git a/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json b/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json
index 1c92ab96..4282ac32 100644
--- a/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json
+++ b/adapters/gamma/gammatest/exemplary/banner-and-video-and-audio.json
@@ -80,7 +80,7 @@
       "mockResponse": {
         "status": 200,
         "body": {
-          "id": "test-request-id",
+          "id": "test-imp-video-id",
           "cur": "USD",
           "seatbid": [
             {
@@ -89,7 +89,10 @@
                 "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
                 "impid": "test-imp-video-id",
                 "price": 0.500000,
-                "adm": "some-test-ad",
+                "adm": "",
+                "adomain": ["sample.com"],
+                "vastXml": "some-test-ad",
+                "vastUrl": "some-test-url",
                 "crid": "29484110",
                 "w": 640,
                 "h": 480
@@ -122,13 +125,15 @@
         "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
         "impid": "test-imp-video-id",
         "price": 0.5,
-        "adm": "some-test-ad",
+        "adm": "",
+        "vastXml": "some-test-ad",
+        "vastUrl": "some-test-url",
         "adid": "29484110",
         "adomain": ["sample.com"],
         "cid": "958",
         "crid": "29484110",
-        "w": 1024,
-        "h": 576
+        "w": 640,
+        "h": 480
       },
       "type": "video"
     }
diff --git a/adapters/gamma/gammatest/exemplary/valid-full-params.json b/adapters/gamma/gammatest/exemplary/valid-full-params.json
index 8fa1d3e7..25d51a64 100644
--- a/adapters/gamma/gammatest/exemplary/valid-full-params.json
+++ b/adapters/gamma/gammatest/exemplary/valid-full-params.json
@@ -2,7 +2,7 @@
   "mockBidRequest": {
     "id": "test-request-id",
     "app":{
-    	"id":"test-app-id",	
+    	"id":"test-app-id",
     	"name":"test-app-name",
     	"bundle":"test-app-bundle"
     },
diff --git a/adapters/gamma/gammatest/supplemental/bad-request.json b/adapters/gamma/gammatest/supplemental/bad-request.json
index d460550c..8b533f91 100644
--- a/adapters/gamma/gammatest/supplemental/bad-request.json
+++ b/adapters/gamma/gammatest/supplemental/bad-request.json
@@ -2,7 +2,7 @@
   "mockBidRequest": {
     "id": "test-request-id",
     "app":{
-    	"id":"test-app-id",	
+    	"id":"test-app-id",
     	"name":"test-app-name",
     	"bundle":"test-app-bundle"
     },
diff --git a/adapters/gamma/gammatest/supplemental/missing-adm.json b/adapters/gamma/gammatest/supplemental/missing-adm.json
new file mode 100644
index 00000000..6b8ff64c
--- /dev/null
+++ b/adapters/gamma/gammatest/supplemental/missing-adm.json
@@ -0,0 +1,76 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-video-id",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 640,
+                    "h": 480
+                },
+                "ext": {
+                    "bidder": {
+                        "id": "sample-id",
+                        "zid": "sample-zone-id",
+                        "wid": "sample-web-id"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://hb.gammaplatform.com/adx/request/?id=sample-id&zid=sample-zone-id&wid=sample-web-id&bidid=test-imp-video-id&hb=pbmobile"
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "cur": "USD",
+                    "seatbid": [
+                        {
+                            "seat": "gamma",
+                            "bid": [
+                                {
+                                    "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                                    "impid": "test-imp-video-id",
+                                    "price": 0.500000,
+                                    "crid": "29484110",
+                                    "w": 640,
+                                    "h": 360
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        }
+    ],
+    "expectedBids": [
+        {
+            "bid": {
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-video-id",
+                "price": 0.500000,
+                "crid": "29484110",
+                "w": 640,
+                "h": 360
+            },
+            "type": "video"
+        }
+    ],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Missing Ad Markup. Run with request.debug = 1 for more info",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/gamma/gammatest/supplemental/missing-param.json b/adapters/gamma/gammatest/supplemental/missing-param.json
index 32e4f9ea..9dac0936 100644
--- a/adapters/gamma/gammatest/supplemental/missing-param.json
+++ b/adapters/gamma/gammatest/supplemental/missing-param.json
@@ -20,7 +20,7 @@
           "bidder": {
             "zid": "sample-zone-id",
             "wid": "sample-web-id"
-          
+
           }
         }
 
diff --git a/adapters/gamma/gammatest/supplemental/missing-zone.json b/adapters/gamma/gammatest/supplemental/missing-zone.json
index b53ddc83..5c84ef22 100644
--- a/adapters/gamma/gammatest/supplemental/missing-zone.json
+++ b/adapters/gamma/gammatest/supplemental/missing-zone.json
@@ -20,7 +20,7 @@
           "bidder": {
             "id": "sample-id",
             "wid": "sample-web-id"
-          
+
           }
         }
 
diff --git a/adapters/gamma/gammatest/supplemental/nobid-signaling.json b/adapters/gamma/gammatest/supplemental/nobid-signaling.json
new file mode 100644
index 00000000..4055ad70
--- /dev/null
+++ b/adapters/gamma/gammatest/supplemental/nobid-signaling.json
@@ -0,0 +1,56 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app":{
+          "id":"test-app-id",
+          "name":"test-app-name",
+          "bundle":"test-app-bundle"
+      },
+  
+      "device":{
+          "ua":"test-device-ua",
+          "ip":"test-device-ip",
+        "ifa":"test-device-ifa",
+        "model":"test-device-model",
+        "os":"test-device-os",
+        "dnt":1
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 50
+              }
+            ]
+          },
+          "ext":{
+            "bidder":{
+              "id": "sample-id",
+              "zid": "sample-zone-id",
+              "wid": "sample-web-id"
+             }
+          }
+        }
+      ]
+    },
+  
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://hb.gammaplatform.com/adx/request/?id=sample-id&zid=sample-zone-id&wid=sample-web-id&bidid=test-imp-id&hb=pbmobile&device_ip=test-device-ip&device_model=test-device-model&device_os=test-device-os&device_ua=test-device-ua&device_ifa=test-device-ifa&app_id=test-app-id&app_bundle=test-app-bundle&app_name=test-app-name"
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [
+                ]
+            }
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/gamma/gammatest/supplemental/status-forbidden.json b/adapters/gamma/gammatest/supplemental/status-forbidden.json
index 950a45ec..3a30b210 100644
--- a/adapters/gamma/gammatest/supplemental/status-forbidden.json
+++ b/adapters/gamma/gammatest/supplemental/status-forbidden.json
@@ -2,7 +2,7 @@
   "mockBidRequest": {
     "id": "test-request-id",
     "app":{
-    	"id":"test-app-id",	
+    	"id":"test-app-id",
     	"name":"test-app-name",
     	"bundle":"test-app-bundle"
     },
diff --git a/adapters/gamma/gammatest/supplemental/status-no-content.json b/adapters/gamma/gammatest/supplemental/status-no-content.json
index 6c7878a8..045fb939 100644
--- a/adapters/gamma/gammatest/supplemental/status-no-content.json
+++ b/adapters/gamma/gammatest/supplemental/status-no-content.json
@@ -2,7 +2,7 @@
   "mockBidRequest": {
     "id": "test-request-id",
     "app":{
-    	"id":"test-app-id",	
+    	"id":"test-app-id",
     	"name":"test-app-name",
     	"bundle":"test-app-bundle"
     },
diff --git a/adapters/info_test.go b/adapters/info_test.go
index 9c0dd16b..ce0f8869 100644
--- a/adapters/info_test.go
+++ b/adapters/info_test.go
@@ -24,6 +24,7 @@ func TestAppNotSupported(t *testing.T) {
 	}
 	constrained := adapters.EnforceBidderInfo(bidder, info)
 	bids, errs := constrained.MakeRequests(&openrtb.BidRequest{
+		Imp: []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}},
 		App: &openrtb.App{},
 	}, &adapters.ExtraRequestInfo{})
 	if !assert.Len(t, errs, 1) {
@@ -45,6 +46,7 @@ func TestSiteNotSupported(t *testing.T) {
 	}
 	constrained := adapters.EnforceBidderInfo(bidder, info)
 	bids, errs := constrained.MakeRequests(&openrtb.BidRequest{
+		Imp:  []openrtb.Imp{{ID: "imp-1", Banner: &openrtb.Banner{}}},
 		Site: &openrtb.Site{},
 	}, &adapters.ExtraRequestInfo{})
 	if !assert.Len(t, errs, 1) {
@@ -69,48 +71,111 @@ func TestImpFiltering(t *testing.T) {
 	}
 
 	constrained := adapters.EnforceBidderInfo(bidder, info)
-	_, errs := constrained.MakeRequests(&openrtb.BidRequest{
-		Imp: []openrtb.Imp{
-			{
-				ID:    "imp-1",
-				Video: &openrtb.Video{},
+
+	testCases := []struct {
+		description    string
+		inBidRequest   *openrtb.BidRequest
+		expectedErrors []error
+		expectedImpLen int
+	}{
+		{
+			description: "Empty Imp array. MakeRequest() call not expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp:  []openrtb.Imp{},
+				Site: &openrtb.Site{},
 			},
-			{
-				Native: &openrtb.Native{},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"},
 			},
-			{
-				ID:     "imp-2",
-				Video:  &openrtb.Video{},
-				Native: &openrtb.Native{},
+			expectedImpLen: 0,
+		},
+		{
+			description: "Sole imp in bid request is of wrong media type. MakeRequest() call not expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{{ID: "imp-1", Video: &openrtb.Video{}}},
+				App: &openrtb.App{},
 			},
-			{
-				Banner: &openrtb.Banner{},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"},
 			},
+			expectedImpLen: 0,
+		},
+		{
+			description: "All imps in bid request of wrong media type, MakeRequest() call not expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{
+					{ID: "imp-1", Video: &openrtb.Video{}},
+					{ID: "imp-2", Native: &openrtb.Native{}},
+					{ID: "imp-3", Audio: &openrtb.Audio{}},
+				},
+				App: &openrtb.App{},
+			},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "request.imp[0] uses video, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[2] uses audio, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "Bid request didn't contain media types supported by the bidder"},
+			},
+			expectedImpLen: 0,
+		},
+		{
+			description: "Some imps with correct media type, MakeRequest() call expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{
+					{
+						ID:    "imp-1",
+						Video: &openrtb.Video{},
+					},
+					{
+						Native: &openrtb.Native{},
+					},
+					{
+						ID:     "imp-2",
+						Video:  &openrtb.Video{},
+						Native: &openrtb.Native{},
+					},
+					{
+						Banner: &openrtb.Banner{},
+					},
+				},
+				Site: &openrtb.Site{},
+			},
+			expectedErrors: []error{
+				&errortypes.BadInput{Message: "request.imp[1] uses native, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[2] uses native, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[3] uses banner, but this bidder doesn't support it"},
+				&errortypes.BadInput{Message: "request.imp[1] has no supported MediaTypes. It will be ignored"},
+				&errortypes.BadInput{Message: "request.imp[3] has no supported MediaTypes. It will be ignored"},
+			},
+			expectedImpLen: 2,
+		},
+		{
+			description: "All imps with correct media type, MakeRequest() call expected",
+			inBidRequest: &openrtb.BidRequest{
+				Imp: []openrtb.Imp{
+					{ID: "imp-1", Video: &openrtb.Video{}},
+					{ID: "imp-2", Video: &openrtb.Video{}},
+				},
+				Site: &openrtb.Site{},
+			},
+			expectedErrors: nil,
+			expectedImpLen: 2,
 		},
-		Site: &openrtb.Site{},
-	}, &adapters.ExtraRequestInfo{})
-	if !assert.Len(t, errs, 6) {
-		return
 	}
-	assert.EqualError(t, errs[0], "request.imp[1] uses native, but this bidder doesn't support it")
-	assert.EqualError(t, errs[1], "request.imp[2] uses native, but this bidder doesn't support it")
-	assert.EqualError(t, errs[2], "request.imp[3] uses banner, but this bidder doesn't support it")
-	assert.EqualError(t, errs[3], "request.imp[1] has no supported MediaTypes. It will be ignored")
-	assert.EqualError(t, errs[4], "request.imp[3] has no supported MediaTypes. It will be ignored")
-	assert.EqualError(t, errs[5], "mock MakeRequests error")
-	assert.IsType(t, &errortypes.BadInput{}, errs[0])
-	assert.IsType(t, &errortypes.BadInput{}, errs[1])
-	assert.IsType(t, &errortypes.BadInput{}, errs[2])
-	assert.IsType(t, &errortypes.BadInput{}, errs[3])
-	assert.IsType(t, &errortypes.BadInput{}, errs[4])
 
-	req := bidder.gotRequest
-	if !assert.Len(t, req.Imp, 2) {
-		return
+	for _, test := range testCases {
+		actualAdapterRequests, actualErrs := constrained.MakeRequests(test.inBidRequest, &adapters.ExtraRequestInfo{})
+
+		// Assert the request.Imp slice was correctly filtered and if MakeRequest() was called by asserting
+		// the corresponding error messages were returned
+		for i, expectedErr := range test.expectedErrors {
+			assert.EqualError(t, expectedErr, actualErrs[i].Error(), "Test failed. Error[%d] in error list mismatch: %s", i, test.description)
+		}
+
+		// Extra MakeRequests() call check: our mockBidder returns an adapter request for every imp
+		assert.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect lenght of filtered imps: %s", test.description)
 	}
-	assert.Equal(t, "imp-1", req.Imp[0].ID)
-	assert.Equal(t, "imp-2", req.Imp[1].ID)
-	assert.Nil(t, req.Imp[1].Native)
 }
 
 type mockBidder struct {
@@ -118,8 +183,13 @@ type mockBidder struct {
 }
 
 func (m *mockBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	m.gotRequest = request
-	return nil, []error{errors.New("mock MakeRequests error")}
+	var adapterRequests []*adapters.RequestData
+
+	for i := 0; i < len(request.Imp); i++ {
+		adapterRequests = append(adapterRequests, &adapters.RequestData{})
+	}
+
+	return adapterRequests, nil
 }
 
 func (m *mockBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json
new file mode 100644
index 00000000..f2896d3d
--- /dev/null
+++ b/adapters/smaato/smaatotest/exemplary/video.json
@@ -0,0 +1,187 @@
+{
+    "mockBidRequest": {
+        "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+        "imp": [
+            {
+                "id": "postbid_iframe",
+                "video": {
+                    "mimes": [
+                        "video/mp4",
+                        "video/quicktime",
+                        "video/3gpp",
+                        "video/x-m4v"
+                    ],
+                    "minduration": 5,
+                    "maxduration": 30,
+                    "protocols": [
+                        7
+                    ],
+                    "w": 1024,
+                    "h": 768,
+                    "startdelay": 0,
+                    "linearity": 1,
+                    "skip": 1,
+                    "skipmin": 5,
+                    "api": [
+                        7
+                    ],
+                    "ext": {
+                        "rewarded": 0
+                    }
+                },
+                "ext": {
+                    "bidder": {
+                        "publisherId": "1100042525",
+                        "adspaceId": "130563103"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "publisher": {
+                "id": "1100042525"
+            },
+            "ext": {
+                "data": {},
+                "amp": 0
+            }
+        },
+        "device": {
+            "ua": "test-user-agent"
+        },
+        "user": {
+            "ext": {
+                "data": {}
+            }
+        },
+        "ext": {
+            "prebid": {
+                "auctiontimestamp": 1598262728811,
+                "targeting": {
+                    "includewinners": true,
+                    "includebidderkeys": false
+                }
+            }
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ]
+                },
+                "uri": "https://prebid/bidder",
+                "body": {
+                    "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+                    "imp": [
+                        {
+                            "id": "postbid_iframe",
+                            "tagid": "130563103",
+                            "video": {
+                                "w": 1024,
+                                "h": 768,
+                                "ext": {
+                                    "rewarded": 0
+                                },
+                                "mimes": [
+                                    "video/mp4",
+                                    "video/quicktime",
+                                    "video/3gpp",
+                                    "video/x-m4v"
+                                ],
+                                "minduration": 5,
+                                "startdelay": 0,
+                                "linearity": 1,
+                                "maxduration": 30,
+                                "skip": 1,
+                                "protocols": [
+                                    7
+                                ],
+                                "skipmin": 5,
+                                "api": [
+                                    7
+                                ]
+                            }
+                        }
+                    ],
+                    "user": {
+                        "ext": {
+                        }
+                    },
+                    "device": {
+                        "ua": "test-user-agent"
+                    },
+                    "site": {
+                        "publisher": {
+                            "id": "1100042525"
+                        }
+                    },
+                    "ext": {
+                        "client": "prebid_server_0.1"
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+                    "seatbid": [
+                        {
+                            "seat": "CM6523",
+                            "bid": [
+                                {
+                                    "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><VAST version=\"2.0\"></VAST>",
+                                    "adomain": [
+                                        "smaato.com"
+                                    ],
+                                    "bidderName": "smaato",
+                                    "cid": "CM6523",
+                                    "crid": "CR69381",
+                                    "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+                                    "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+                                    "iurl": "https://iurl",
+                                    "nurl": "https://nurl",
+                                    "price": 0.01,
+                                    "w": 1024,
+                                    "h": 768
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><VAST version=\"2.0\"></VAST>",
+                        "adomain": [
+                            "smaato.com"
+                        ],
+                        "cid": "CM6523",
+                        "crid": "CR69381",
+                        "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+                        "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+                        "iurl": "https://iurl",
+                        "nurl": "https://nurl",
+                        "price": 0.01,
+                        "w": 1024,
+                        "h": 768
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
index 6d4990e9..1fce58f0 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
@@ -162,5 +162,11 @@
       }
     }
   ],
-  "expectedBidResponses": []
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"<div>hello</div>\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}",
+      "comparison": "literal"
+    }
+  ]
 }
\ No newline at end of file
diff --git a/analytics/pubstack/eventchannel/eventchannel_test.go b/analytics/pubstack/eventchannel/eventchannel_test.go
index 9fdcfe97..792e15e1 100644
--- a/analytics/pubstack/eventchannel/eventchannel_test.go
+++ b/analytics/pubstack/eventchannel/eventchannel_test.go
@@ -134,3 +134,42 @@ func TestEventChannel_Push(t *testing.T) {
 	assert.Equal(t, string(data), "onetwothreefourfivesixseven")
 
 }
+
+func TestEventChannel_OutputFormat(t *testing.T) {
+
+	toGzip := func(payload string) []byte {
+		var buf bytes.Buffer
+		zw := gzip.NewWriter(&buf)
+
+		if _, err := zw.Write([]byte(payload)); err != nil {
+			assert.Fail(t, err.Error())
+		}
+
+		if err := zw.Close(); err != nil {
+			assert.Fail(t, err.Error())
+		}
+		return buf.Bytes()
+	}
+
+	data := make([]byte, 0)
+	send := func(payload []byte) error {
+		data = append(data, payload...)
+		return nil
+	}
+
+	eventChannel := NewEventChannel(send, 15000, 10, 2*time.Minute)
+
+	eventChannel.Push([]byte("one"))
+	eventChannel.flush()
+	eventChannel.Push([]byte("two"))
+	eventChannel.Push([]byte("three"))
+
+	eventChannel.Close()
+
+	time.Sleep(10 * time.Millisecond)
+
+	expected := append(toGzip("one"), toGzip("twothree")...)
+
+	assert.Equal(t, expected, data)
+
+}
diff --git a/config/config_test.go b/config/config_test.go
index 3da3f721..40589ac7 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -90,6 +90,21 @@ func TestExternalCacheURLValidate(t *testing.T) {
 			data:      ExternalCache{Host: "http://", Path: ""},
 			expErrors: 1,
 		},
+		{
+			desc:      "Scheme Invalid",
+			data:      ExternalCache{Scheme: "invalid", Host: "www.google.com", Path: "/path/v1"},
+			expErrors: 1,
+		},
+		{
+			desc:      "Scheme HTTP",
+			data:      ExternalCache{Scheme: "http", Host: "www.google.com", Path: "/path/v1"},
+			expErrors: 0,
+		},
+		{
+			desc:      "Scheme HTTPS",
+			data:      ExternalCache{Scheme: "https", Host: "www.google.com", Path: "/path/v1"},
+			expErrors: 0,
+		},
 	}
 	for _, test := range testCases {
 		var errs configErrors
@@ -150,6 +165,7 @@ cache:
   host: prebidcache.net
   query: uuid=%PBS_CACHE_UUID%
 external_cache:
+  scheme: https
   host: www.externalprebidcache.net
   path: /endpoints/cache
 http_client:
@@ -307,6 +323,7 @@ func TestFullConfig(t *testing.T) {
 	cmpStrings(t, "cache.scheme", cfg.CacheURL.Scheme, "http")
 	cmpStrings(t, "cache.host", cfg.CacheURL.Host, "prebidcache.net")
 	cmpStrings(t, "cache.query", cfg.CacheURL.Query, "uuid=%PBS_CACHE_UUID%")
+	cmpStrings(t, "external_cache.scheme", cfg.ExtCacheURL.Scheme, "https")
 	cmpStrings(t, "external_cache.host", cfg.ExtCacheURL.Host, "www.externalprebidcache.net")
 	cmpStrings(t, "external_cache.path", cfg.ExtCacheURL.Path, "/endpoints/cache")
 	cmpInts(t, "http_client.max_connections_per_host", cfg.Client.MaxConnsPerHost, 10)
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 534db3c7..78715f5c 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1259,8 +1259,8 @@ func (m *mockCacheClient) PutJson(ctx context.Context, values []prebid_cache_cli
 	return []string{}, []error{}
 }
 
-func (m *mockCacheClient) GetExtCacheData() (string, string) {
-	return "", ""
+func (m *mockCacheClient) GetExtCacheData() (scheme string, host string, path string) {
+	return "", "", ""
 }
 
 type mockVideoStoredReqFetcher struct {
diff --git a/exchange/auction_test.go b/exchange/auction_test.go
index e23c45cb..9f24682b 100644
--- a/exchange/auction_test.go
+++ b/exchange/auction_test.go
@@ -298,22 +298,27 @@ type pbsBid struct {
 	Bidder  openrtb_ext.BidderName `json:"bidder"`
 }
 
-type mockCache struct {
-	items []prebid_cache_client.Cacheable
-}
-
 type cacheComparator struct {
 	freq         int
 	expectedKeys []string
 	actualKeys   []string
 }
 
-func (c *mockCache) GetExtCacheData() (string, string) {
-	return "", ""
+type mockCache struct {
+	scheme string
+	host   string
+	path   string
+	items  []prebid_cache_client.Cacheable
+}
+
+func (c *mockCache) GetExtCacheData() (scheme string, host string, path string) {
+	return c.scheme, c.host, c.path
 }
+
 func (c *mockCache) GetPutUrl() string {
 	return ""
 }
+
 func (c *mockCache) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) {
 	c.items = values
 	return []string{"", "", "", "", ""}, nil
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index d1531237..efabb845 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -274,9 +274,10 @@ func TestDebugBehaviour(t *testing.T) {
 	}
 }
 
-func TestGetBidCacheInfo(t *testing.T) {
+func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	testUUID := "CACHE_UUID_1234"
-	testExternalCacheHost := "https://www.externalprebidcache.net"
+	testExternalCacheScheme := "https"
+	testExternalCacheHost := "www.externalprebidcache.net"
 	testExternalCachePath := "endpoints/cache"
 
 	/* 1) An adapter 											*/
@@ -292,8 +293,9 @@ func TestGetBidCacheInfo(t *testing.T) {
 			Host: "www.internalprebidcache.net",
 		},
 		ExtCacheURL: config.ExternalCache{
-			Host: testExternalCacheHost,
-			Path: testExternalCachePath,
+			Scheme: testExternalCacheScheme,
+			Host:   testExternalCacheHost,
+			Path:   testExternalCachePath,
 		},
 	}
 	adapterList := make([]openrtb_ext.BidderName, 0, 2)
@@ -421,7 +423,7 @@ func TestGetBidCacheInfo(t *testing.T) {
 				Seat: string(bidderName),
 				Bid: []openrtb.Bid{
 					{
-						Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`),
+						Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheScheme + `://` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`),
 					},
 				},
 			},
@@ -446,6 +448,131 @@ func TestGetBidCacheInfo(t *testing.T) {
 	assert.Equal(t, expCacheURL, cacheURL, "[TestGetBidCacheInfo] cacheId field in ext should equal \"%s\" \n", expCacheURL)
 }
 
+func TestGetBidCacheInfo(t *testing.T) {
+	bid := &openrtb.Bid{ID: "42"}
+	testCases := []struct {
+		description      string
+		scheme           string
+		host             string
+		path             string
+		bid              *pbsOrtbBid
+		auction          *auction
+		expectedFound    bool
+		expectedCacheID  string
+		expectedCacheURL string
+	}{
+		{
+			description:      "JSON Cache ID",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "https://prebid.org/cache?uuid=anyID",
+		},
+		{
+			description:      "VAST Cache ID",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{vastCacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "https://prebid.org/cache?uuid=anyID",
+		},
+		{
+			description:      "Cache ID Not Found",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{},
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Scheme Not Provided",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "prebid.org/cache?uuid=anyID",
+		},
+		{
+			description:      "Host And Path Not Provided - Without Scheme",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Host And Path Not Provided - With Scheme",
+			scheme:           "https",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    true,
+			expectedCacheID:  "anyID",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Nil Bid",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              nil,
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Nil Embedded Bid",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: nil},
+			auction:          &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}},
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+		{
+			description:      "Nil Auction",
+			scheme:           "https",
+			host:             "prebid.org",
+			path:             "cache",
+			bid:              &pbsOrtbBid{bid: bid},
+			auction:          nil,
+			expectedFound:    false,
+			expectedCacheID:  "",
+			expectedCacheURL: "",
+		},
+	}
+
+	for _, test := range testCases {
+		exchange := &exchange{
+			cache: &mockCache{
+				scheme: test.scheme,
+				host:   test.host,
+				path:   test.path,
+			},
+		}
+
+		cacheInfo, found := exchange.getBidCacheInfo(test.bid, test.auction)
+
+		assert.Equal(t, test.expectedFound, found, test.description+":found")
+		assert.Equal(t, test.expectedCacheID, cacheInfo.CacheId, test.description+":id")
+		assert.Equal(t, test.expectedCacheURL, cacheInfo.Url, test.description+":url")
+	}
+}
+
 func TestBidResponseCurrency(t *testing.T) {
 	// Init objects
 	cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)}
@@ -2176,8 +2303,8 @@ func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Hand
 
 type wellBehavedCache struct{}
 
-func (c *wellBehavedCache) GetExtCacheData() (string, string) {
-	return "www.pbcserver.com", "/pbcache/endpoint"
+func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) {
+	return "https", "www.pbcserver.com", "/pbcache/endpoint"
 }
 
 func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) {
diff --git a/exchange/exchangetest/targeting-cache-vast.json b/exchange/exchangetest/targeting-cache-vast.json
index f348dd1b..53a48c4e 100644
--- a/exchange/exchangetest/targeting-cache-vast.json
+++ b/exchange/exchangetest/targeting-cache-vast.json
@@ -67,7 +67,7 @@
                 "cache": {
                   "bids": {
                     "cacheId": "0",
-                    "url": "www.pbcserver.com/pbcache/endpoint?uuid=0"
+                    "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0"
                   },
                   "key": "",
                   "url": ""
diff --git a/exchange/exchangetest/targeting-cache-zero.json b/exchange/exchangetest/targeting-cache-zero.json
index 51301530..0048ea10 100644
--- a/exchange/exchangetest/targeting-cache-zero.json
+++ b/exchange/exchangetest/targeting-cache-zero.json
@@ -70,7 +70,7 @@
                 "cache": {
                   "bids": {
                     "cacheId": "0",
-                    "url": "www.pbcserver.com/pbcache/endpoint?uuid=0"
+                    "url": "https://www.pbcserver.com/pbcache/endpoint?uuid=0"
                   },
                   "key": "",
                   "url": ""
diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go
index 5840d4ea..72fd2761 100644
--- a/prebid_cache_client/client_test.go
+++ b/prebid_cache_client/client_test.go
@@ -186,58 +186,80 @@ func TestEncodeValueToBuffer(t *testing.T) {
 func TestStripCacheHostAndPath(t *testing.T) {
 	inCacheURL := config.Cache{ExpectedTimeMillis: 10}
 	type aTest struct {
-		inExtCacheURL config.ExternalCache
-		expectedHost  string
-		expectedPath  string
+		inExtCacheURL  config.ExternalCache
+		expectedScheme string
+		expectedHost   string
+		expectedPath   string
 	}
 	testInput := []aTest{
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebid-server.prebid.org",
-				Path: "/pbcache/endpoint",
+				Scheme: "",
+				Host:   "prebid-server.prebid.org",
+				Path:   "/pbcache/endpoint",
 			},
-			expectedHost: "prebid-server.prebid.org",
-			expectedPath: "/pbcache/endpoint",
+			expectedScheme: "",
+			expectedHost:   "prebid-server.prebid.org",
+			expectedPath:   "/pbcache/endpoint",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebidcache.net",
-				Path: "",
+				Scheme: "https",
+				Host:   "prebid-server.prebid.org",
+				Path:   "/pbcache/endpoint",
 			},
-			expectedHost: "prebidcache.net",
-			expectedPath: "",
+			expectedScheme: "https",
+			expectedHost:   "prebid-server.prebid.org",
+			expectedPath:   "/pbcache/endpoint",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "",
-				Path: "",
+				Scheme: "",
+				Host:   "prebidcache.net",
+				Path:   "",
 			},
-			expectedHost: "",
-			expectedPath: "",
+			expectedScheme: "",
+			expectedHost:   "prebidcache.net",
+			expectedPath:   "",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebid-server.prebid.org",
-				Path: "pbcache/endpoint",
+				Scheme: "",
+				Host:   "",
+				Path:   "",
 			},
-			expectedHost: "prebid-server.prebid.org",
-			expectedPath: "/pbcache/endpoint",
+			expectedScheme: "",
+			expectedHost:   "",
+			expectedPath:   "",
 		},
 		{
 			inExtCacheURL: config.ExternalCache{
-				Host: "prebidcache.net",
-				Path: "/",
+				Scheme: "",
+				Host:   "prebid-server.prebid.org",
+				Path:   "pbcache/endpoint",
 			},
-			expectedHost: "prebidcache.net",
-			expectedPath: "",
+			expectedScheme: "",
+			expectedHost:   "prebid-server.prebid.org",
+			expectedPath:   "/pbcache/endpoint",
+		},
+		{
+			inExtCacheURL: config.ExternalCache{
+				Scheme: "",
+				Host:   "prebidcache.net",
+				Path:   "/",
+			},
+			expectedScheme: "",
+			expectedHost:   "prebidcache.net",
+			expectedPath:   "",
 		},
 	}
 	for _, test := range testInput {
 		cacheClient := NewClient(&http.Client{}, &inCacheURL, &test.inExtCacheURL, &metricsConf.DummyMetricsEngine{})
-		cHost, cPath := cacheClient.GetExtCacheData()
+		scheme, host, path := cacheClient.GetExtCacheData()
 
-		assert.Equal(t, test.expectedHost, cHost)
-		assert.Equal(t, test.expectedPath, cPath)
+		assert.Equal(t, test.expectedScheme, scheme)
+		assert.Equal(t, test.expectedHost, host)
+		assert.Equal(t, test.expectedPath, path)
 	}
 }