Repository: prebid/prebid-server
Pull request #2179 (2022-03-09 05:46:02)

I1725 vast event trackers - Support for EventType = Vast in Analytics Endpoint
@bretg / @mansinahar / @SyntaxNode Please review this PR. This PR is addressing the changes for supporting EventType = Vast and vtype parameter in Analytics Endpoint

Prebid Server Video Events - Phase I -https://docs.google.com/document/d/1zzi06jWpYsvhuiQ_3KCpes0DpmpO2teatmo4V5kIhi0/edit?usp=sharing
Review Document - https://docs.google.com/document/d/1tH6I9Vho-_KaUIGNCMum9JwbxHDc0FyG2wD8T-h3wkY/edit?usp=sharing
PRD - https://docs.google.com/document/d/1a-U1DytiCTtacM27qH3OZwhPa7bDwQzfpVRHDhyikWw/edit#heading=h.x6fobe7fnos0


Patch:
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
new file mode 100644
index 00000000..570114b0
--- /dev/null
+++ b/.github/workflows/security.yml
@@ -0,0 +1,32 @@
+name: Security Check
+
+on:
+  push:
+    branches: [master]
+  pull_request:
+    branches: [master]
+
+jobs:
+  build:
+    name: Trivy
+    runs-on: ubuntu-18.04
+    steps:
+      - name: Checkout Code
+        uses: actions/checkout@v2
+        with:
+          # Resolves to empty string for push events and falls back to HEAD.
+          ref: ${{ github.event.pull_request.head.sha }}
+
+      - name: Run Trivy
+        uses: aquasecurity/trivy-action@master
+        with:
+          scan-type: 'fs'
+          ignore-unfixed: true
+          format: 'sarif'
+          output: 'trivy-results.sarif'
+          severity: 'CRITICAL,HIGH'
+          
+      - name: Upload Results To GitHub Security Tab
+        uses: github/codeql-action/upload-sarif@v1
+        with:
+          sarif_file: 'trivy-results.sarif'
\ No newline at end of file
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index d8f969b5..fc36f45c 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -20,7 +20,7 @@ jobs:
       with:
         go-version: ${{ matrix.go-version }}
 
-    - name: Checkout Branch
+    - name: Checkout Code
       uses: actions/checkout@v2
       with:
         # Resolves to empty string for push events and falls back to HEAD.
diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go
index 9ece01db..2c29e69a 100644
--- a/adapters/adkernelAdn/adkernelAdn.go
+++ b/adapters/adkernelAdn/adkernelAdn.go
@@ -15,8 +15,6 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
-const defaultDomain string = "tag.adkernel.com"
-
 type adkernelAdnAdapter struct {
 	EndpointTemplate *template.Template
 }
@@ -103,7 +101,6 @@ func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkern
 			res[impExt] = make([]openrtb2.Imp, 0)
 		}
 		res[impExt] = append(res[impExt], imp)
-
 	}
 	return res, errors
 }
@@ -212,12 +209,8 @@ func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext
 
 // Builds enpoint url based on adapter-specific pub settings from imp.ext
 func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdkernelAdn) (string, error) {
-	reqHost := defaultDomain
-	if params.Host != "" {
-		reqHost = params.Host
-	}
 	pubIDStr := strconv.Itoa(params.PublisherID)
-	endpointParams := macros.EndpointTemplateParams{Host: reqHost, PublisherID: pubIDStr}
+	endpointParams := macros.EndpointTemplateParams{PublisherID: pubIDStr}
 	return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
 }
 
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index f855f917..79f07200 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -271,6 +271,14 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP
 		imp.Banner = bannerCopy
 	}
 
+	if pubmaticExt.Kadfloor != "" {
+		bidfloor, err := strconv.ParseFloat(strings.TrimSpace(pubmaticExt.Kadfloor), 64)
+		if err == nil {
+			//do not overwrite existing value if kadfloor is invalid
+			imp.BidFloor = bidfloor
+		}
+	}
+
 	extMap := make(map[string]interface{}, 0)
 	if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 {
 		addKeywordsToExt(pubmaticExt.Keywords, extMap)
diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go
index e0a451a9..d36312e5 100644
--- a/adapters/telaria/telaria.go
+++ b/adapters/telaria/telaria.go
@@ -182,41 +182,35 @@ func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *a
 	var seatCode string
 	originalPublisherID := a.FetchOriginalPublisherID(&request)
 
-	var errors []error
 	var telariaImpExt *openrtb_ext.ExtImpTelaria
 	var err error
-	for i, imp := range request.Imp {
-		// fetch adCode & seatCode from Imp[i].Ext
-		telariaImpExt, err = a.FetchTelariaExtImpParams(&imp)
-		if err != nil {
-			errors = append(errors, err)
-			break
-		}
 
-		seatCode = telariaImpExt.SeatCode
+	var imp = request.Imp[0]
+	// fetch adCode & seatCode from Imp[i].Ext
+	telariaImpExt, err = a.FetchTelariaExtImpParams(&imp)
+	if err != nil {
+		return nil, []error{err}
+	}
 
-		// move the original tagId and the original publisher.id into the Imp[i].Ext object
-		request.Imp[i].Ext, err = json.Marshal(&ImpressionExtOut{request.Imp[i].TagID, originalPublisherID})
-		if err != nil {
-			errors = append(errors, err)
-			break
-		}
+	seatCode = telariaImpExt.SeatCode
 
-		// Swap the tagID with adCode
-		request.Imp[i].TagID = telariaImpExt.AdCode
+	// move the original tagId and the original publisher.id into the Imp[i].Ext object
+	imp.Ext, err = json.Marshal(&ImpressionExtOut{imp.TagID, originalPublisherID})
+	if err != nil {
+		return nil, []error{err}
 	}
 
+	// Swap the tagID with adCode
+	imp.TagID = telariaImpExt.AdCode
+
 	// Add the Extra from Imp to the top level Ext
 	if telariaImpExt != nil && telariaImpExt.Extra != nil {
 		request.Ext, err = json.Marshal(&telariaBidExt{Extra: telariaImpExt.Extra})
 		if err != nil {
-			errors = append(errors, err)
+			return nil, []error{err}
 		}
 	}
-
-	if len(errors) > 0 {
-		return nil, errors
-	}
+	request.Imp = []openrtb2.Imp{imp}
 
 	// Add seatCode to <Site/App>.Publisher.ID
 	siteObject, appObject := a.PopulatePublisherId(&request, seatCode)
@@ -282,7 +276,12 @@ func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
 	sb := bidResp.SeatBid[0]
 
-	for _, bid := range sb.Bid {
+	for i := range sb.Bid {
+		bid := sb.Bid[i]
+		if i >= len(internalRequest.Imp) {
+			break
+		}
+		bid.ImpID = internalRequest.Imp[i].ID
 		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 			Bid:     &bid,
 			BidType: openrtb_ext.BidTypeVideo,
diff --git a/amp/parse.go b/amp/parse.go
index 05e43aca..1ce19a76 100644
--- a/amp/parse.go
+++ b/amp/parse.go
@@ -9,7 +9,7 @@ import (
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
 )
 
-// Params defines the paramters of an AMP request.
+// Params defines the parameters of an AMP request.
 type Params struct {
 	Account         string
 	CanonicalURL    string
@@ -31,7 +31,7 @@ type Size struct {
 	Width          int64
 }
 
-// ParseParams parses the AMP paramters from a HTTP request.
+// ParseParams parses the AMP parameters from a HTTP request.
 func ParseParams(httpRequest *http.Request) (Params, error) {
 	query := httpRequest.URL.Query()
 
@@ -104,7 +104,7 @@ func chooseConsent(consent, gdprConsent string) string {
 		return consent
 	}
 
-	// Fallback to 'gdpr_consent' for compatability until it's no longer used. This was our original
+	// Fallback to 'gdpr_consent' for compatibility until it's no longer used. This was our original
 	// implementation before the same AMP macro was reused for CCPA.
 	return gdprConsent
 }
diff --git a/analytics/event.go b/analytics/event.go
index 56b75769..0b0e65f0 100644
--- a/analytics/event.go
+++ b/analytics/event.go
@@ -5,8 +5,9 @@ type EventType string
 
 // Possible values of events Prebid Server can receive for an ad.
 const (
-	Win EventType = "win"
-	Imp EventType = "imp"
+	Win  EventType = "win"
+	Imp  EventType = "imp"
+	Vast EventType = "vast"
 )
 
 // ResponseFormat enumerates the values of a Prebid Server event.
@@ -19,6 +20,18 @@ const (
 	Image ResponseFormat = "i"
 )
 
+// VastType enumerates the values of vast type events Prebid Server can receive
+type VastType string
+
+// Possible value of VastType event prebid server can receive.
+const (
+	Start         VastType = "start"
+	FirstQuartile VastType = "firstQuartile"
+	MidPoint      VastType = "midPoint"
+	ThirdQuartile VastType = "thirdQuartile"
+	Complete      VastType = "complete"
+)
+
 // Analytics indicates if the notification event should be handled or not
 type Analytics string
 
@@ -36,4 +49,5 @@ type EventRequest struct {
 	Bidder      string         `json:"bidder,omitempty"`
 	Timestamp   int64          `json:"timestamp,omitempty"`
 	Integration string         `json:"integration,omitempty"`
+	VType       VastType       `json:"vtype,omitempty"`
 }
diff --git a/config/accounts.go b/config/accounts.go
index 0e6c4114..ae9d656e 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -13,15 +13,24 @@ const (
 
 // Account represents a publisher account configuration
 type Account struct {
-	ID                 string      `mapstructure:"id" json:"id"`
-	Disabled           bool        `mapstructure:"disabled" json:"disabled"`
-	CacheTTL           DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
-	EventsEnabled      bool        `mapstructure:"events_enabled" json:"events_enabled"`
-	CCPA               AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
-	GDPR               AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
-	DebugAllow         bool        `mapstructure:"debug_allow" json:"debug_allow"`
-	DefaultIntegration string      `mapstructure:"default_integration" json:"default_integration"`
-	Events             Events      `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
+	ID                      string      `mapstructure:"id" json:"id"`
+	Disabled                bool        `mapstructure:"disabled" json:"disabled"`
+	CacheTTL                DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
+	EventsEnabled           bool        `mapstructure:"events_enabled" json:"events_enabled"`
+	CCPA                    AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
+	GDPR                    AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
+	DebugAllow              bool        `mapstructure:"debug_allow" json:"debug_allow"`
+	DefaultIntegration      string      `mapstructure:"default_integration" json:"default_integration"`
+	CookieSync              CookieSync  `mapstructure:"cookie_sync" json:"cookie_sync"`
+	Events                  Events      `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725
+	TruncateTargetAttribute *int        `mapstructure:"truncate_target_attr" json:"truncate_target_attr"`
+}
+
+// CookieSync represents the account-level defaults for the cookie sync endpoint.
+type CookieSync struct {
+	DefaultLimit    int  `mapstructure:"default_limit" json:"default_limit"`
+	MaxLimit        int  `mapstructure:"max_limit" json:"max_limit"`
+	DefaultCoopSync bool `mapstructure:"default_coop_sync" json:"default_coop_sync"`
 }
 
 // AccountCCPA represents account-specific CCPA configuration
diff --git a/config/config.go b/config/config.go
index 57c9b5a6..9324acce 100644
--- a/config/config.go
+++ b/config/config.go
@@ -819,7 +819,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1")
 	v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json")
 	v.SetDefault("adapters.adkernel.endpoint", "https://pbs.adksrv.com/hb?zone={{.ZoneID}}")
-	v.SetDefault("adapters.adkerneladn.endpoint", "http://{{.Host}}/rtbpub?account={{.PublisherID}}")
+	v.SetDefault("adapters.adkerneladn.endpoint", "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}")
 	v.SetDefault("adapters.adman.endpoint", "http://pub.admanmedia.com/?c=o&m=ortb")
 	v.SetDefault("adapters.admixer.endpoint", "http://inv-nets.admixer.net/pbs.aspx")
 	v.SetDefault("adapters.adocean.endpoint", "https://{{.Host}}")
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 079348be..12adc8ad 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -8,17 +8,21 @@ import (
 	"io/ioutil"
 	"net/http"
 	"strconv"
+	"strings"
 
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
+	accountService "github.com/prebid/prebid-server/account"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/privacy"
 	"github.com/prebid/prebid-server/privacy/ccpa"
 	gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/prebid/prebid-server/usersync"
 )
 
@@ -28,6 +32,8 @@ var (
 	errCookieSyncGDPRConsentMissing                = errors.New("gdpr_consent is required if gdpr=1")
 	errCookieSyncGDPRConsentMissingSignalAmbiguous = errors.New("gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request")
 	errCookieSyncInvalidBiddersType                = errors.New("invalid bidders type. must either be a string '*' or a string array of bidders")
+	errCookieSyncAccountBlocked                    = errors.New("account is disabled, please reach out to the prebid server host")
+	errCookieSyncAccountInvalid                    = errors.New("account must be valid if provided, please reach out to the prebid server host")
 )
 
 var cookieSyncBidderFilterAllowAll = usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude)
@@ -38,6 +44,7 @@ func NewCookieSyncEndpoint(
 	gdprPermissions gdpr.Permissions,
 	metrics metrics.MetricsEngine,
 	pbsAnalytics analytics.PBSAnalyticsModule,
+	accountsFetcher stored_requests.AccountFetcher,
 	bidders map[string]openrtb_ext.BidderName) HTTPRouterHandler {
 
 	bidderHashSet := make(map[string]struct{}, len(bidders))
@@ -46,38 +53,38 @@ func NewCookieSyncEndpoint(
 	}
 
 	return &cookieSyncEndpoint{
-		chooser:          usersync.NewChooser(syncersByBidder),
-		config:           config.UserSync,
-		hostCookieConfig: &config.HostCookie,
+		chooser: usersync.NewChooser(syncersByBidder),
+		config:  config,
 		privacyConfig: usersyncPrivacyConfig{
 			gdprConfig:      config.GDPR,
 			gdprPermissions: gdprPermissions,
 			ccpaEnforce:     config.CCPA.Enforce,
 			bidderHashSet:   bidderHashSet,
 		},
-		metrics:      metrics,
-		pbsAnalytics: pbsAnalytics,
+		metrics:         metrics,
+		pbsAnalytics:    pbsAnalytics,
+		accountsFetcher: accountsFetcher,
 	}
 }
 
 type cookieSyncEndpoint struct {
-	chooser          usersync.Chooser
-	config           config.UserSync
-	hostCookieConfig *config.HostCookie
-	privacyConfig    usersyncPrivacyConfig
-	metrics          metrics.MetricsEngine
-	pbsAnalytics     analytics.PBSAnalyticsModule
+	chooser         usersync.Chooser
+	config          *config.Configuration
+	privacyConfig   usersyncPrivacyConfig
+	metrics         metrics.MetricsEngine
+	pbsAnalytics    analytics.PBSAnalyticsModule
+	accountsFetcher stored_requests.AccountFetcher
 }
 
 func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 	request, privacyPolicies, err := c.parseRequest(r)
 	if err != nil {
-		c.metrics.RecordCookieSync(metrics.CookieSyncBadRequest)
+		c.writeParseRequestErrorMetrics(err)
 		c.handleError(w, err, http.StatusBadRequest)
 		return
 	}
 
-	cookie := usersync.ParseCookieFromRequest(r, c.hostCookieConfig)
+	cookie := usersync.ParseCookieFromRequest(r, &c.config.HostCookie)
 
 	result := c.chooser.Choose(request, cookie)
 	switch result.Status {
@@ -125,6 +132,11 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 		}
 	}
 
+	request, err = c.setLimit(request)
+	if err != nil {
+		return usersync.Request{}, privacy.Policies{}, err
+	}
+
 	privacyPolicies := privacy.Policies{
 		GDPR: gdprPrivacy.Policy{
 			Signal:  gdprString,
@@ -154,8 +166,8 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 	rx := usersync.Request{
 		Bidders: request.Bidders,
 		Cooperative: usersync.Cooperative{
-			Enabled:        (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.Cooperative.EnabledByDefault),
-			PriorityGroups: c.config.Cooperative.PriorityGroups,
+			Enabled:        (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault),
+			PriorityGroups: c.config.UserSync.Cooperative.PriorityGroups,
 		},
 		Limit: request.Limit,
 		Privacy: usersyncPrivacy{
@@ -169,6 +181,39 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 	return rx, privacyPolicies, nil
 }
 
+func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) {
+	switch err {
+	case errCookieSyncAccountBlocked:
+		c.metrics.RecordCookieSync(metrics.CookieSyncAccountBlocked)
+	case errCookieSyncAccountInvalid:
+		c.metrics.RecordCookieSync(metrics.CookieSyncAccountInvalid)
+	default:
+		c.metrics.RecordCookieSync(metrics.CookieSyncBadRequest)
+	}
+}
+
+func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest) (cookieSyncRequest, error) {
+	if request.Account != "" {
+		accountInfo, errs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account)
+		if len(errs) > 0 {
+			return request, combineErrors(errs)
+		}
+		if request.Limit <= 0 {
+			request.Limit = accountInfo.CookieSync.DefaultLimit
+		}
+		if request.Limit <= 0 || request.Limit > accountInfo.CookieSync.MaxLimit {
+			request.Limit = accountInfo.CookieSync.MaxLimit
+		}
+		if request.Limit < 0 {
+			request.Limit = 0
+		}
+		if request.CooperativeSync == nil {
+			request.CooperativeSync = &accountInfo.CookieSync.DefaultCoopSync
+		}
+	}
+	return request, nil
+}
+
 func parseTypeFilter(request *cookieSyncRequestFilterSettings) (usersync.SyncTypeFilter, error) {
 	syncTypeFilter := usersync.SyncTypeFilter{
 		IFrame:   cookieSyncBidderFilterAllowAll,
@@ -237,6 +282,23 @@ func (c *cookieSyncEndpoint) handleError(w http.ResponseWriter, err error, httpS
 	})
 }
 
+func combineErrors(errs []error) error {
+	var errorStrings []string
+	for _, err := range errs {
+		// preserve knowledge of special account errors
+		switch errortypes.ReadCode(err) {
+		case errortypes.BlacklistedAcctErrorCode:
+			return errCookieSyncAccountBlocked
+		case errortypes.AcctRequiredErrorCode:
+			return errCookieSyncAccountInvalid
+		}
+
+		errorStrings = append(errorStrings, err.Error())
+	}
+	combinedErrors := strings.Join(errorStrings, " ")
+	return errors.New(combinedErrors)
+}
+
 func (c *cookieSyncEndpoint) writeBidderMetrics(biddersEvaluated []usersync.BidderEvaluation) {
 	for _, bidder := range biddersEvaluated {
 		switch bidder.Status {
@@ -319,6 +381,7 @@ type cookieSyncRequest struct {
 	Limit           int                              `json:"limit"`
 	CooperativeSync *bool                            `json:"coopSync"`
 	FilterSettings  *cookieSyncRequestFilterSettings `json:"filterSettings"`
+	Account         string                           `json:"account"`
 }
 
 type cookieSyncRequestFilterSettings struct {
diff --git a/endpoints/events/event.go b/endpoints/events/event.go
index fd47683a..8c9c252a 100644
--- a/endpoints/events/event.go
+++ b/endpoints/events/event.go
@@ -23,6 +23,7 @@ const (
 	// Required
 	TemplateUrl        = "%v/event?t=%v&b=%v&a=%v"
 	TypeParameter      = "t"
+	VTypeParameter     = "vtype"
 	BidIdParameter     = "b"
 	AccountIdParameter = "a"
 
@@ -144,6 +145,16 @@ func ParseEventRequest(r *http.Request) (*analytics.EventRequest, []error) {
 		errs = append(errs, err)
 	}
 
+	if event.Type == analytics.Vast {
+		if err := readVType(event, r); err != nil {
+			errs = append(errs, err)
+		}
+	} else {
+		if t := r.URL.Query().Get(VTypeParameter); t != "" {
+			errs = append(errs, &errortypes.BadInput{Message: "parameter 'vtype' is only required for t=vast"})
+		}
+	}
+
 	// validate bidid
 	if bidid, err := checkRequiredParameter(r, BidIdParameter); err != nil {
 		errs = append(errs, err)
@@ -261,11 +272,40 @@ func readType(er *analytics.EventRequest, httpRequest *http.Request) error {
 	case string(analytics.Win):
 		er.Type = analytics.Win
 		return nil
+	case string(analytics.Vast):
+		er.Type = analytics.Vast
+		return nil
 	default:
 		return &errortypes.BadInput{Message: fmt.Sprintf("unknown type: '%s'", t)}
 	}
 }
 
+// readVType validates analytics.EventRequest vtype
+func readVType(er *analytics.EventRequest, httpRequest *http.Request) error {
+	vtype, err := checkRequiredParameter(httpRequest, VTypeParameter)
+
+	if err != nil {
+		return err
+	}
+
+	switch vtype {
+	case string(analytics.Start):
+		er.VType = analytics.Start
+	case string(analytics.FirstQuartile):
+		er.VType = analytics.FirstQuartile
+	case string(analytics.MidPoint):
+		er.VType = analytics.MidPoint
+	case string(analytics.ThirdQuartile):
+		er.VType = analytics.ThirdQuartile
+	case string(analytics.Complete):
+		er.VType = analytics.Complete
+	default:
+		return &errortypes.BadInput{Message: fmt.Sprintf("unknown vtype: '%s'", vtype)}
+	}
+
+	return nil
+}
+
 // readFormat validates analytics.EventRequest format attribute
 func readFormat(er *analytics.EventRequest, httpRequest *http.Request) error {
 	f := httpRequest.URL.Query().Get(FormatParameter)
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 074f6739..42caa3a7 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -940,7 +940,7 @@ func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.Cont
 
 func validateNativePlacementType(pt native1.PlacementType, impIndex int) error {
 	if pt == 0 {
-		// Placement Type is only reccomended, not required.
+		// Placement Type is only recommended, not required.
 		return nil
 	}
 	if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 08fd3117..80820ac1 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -158,7 +158,7 @@ type AuctionRequest struct {
 	GlobalPrivacyControlHeader string
 	ImpExtInfoMap              map[string]ImpExtInfo
 
-	// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
+	// LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests
 	// in HoldAuction until we get to factoring it away. Do not use for anything new.
 	LegacyLabels   metrics.Labels
 	FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
@@ -287,7 +287,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 				errs = append(errs, cacheErrs...)
 			}
 
-			targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory)
+			targData.setTargeting(auc, r.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute)
 
 		}
 		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, responseDebugAllow, errs)
diff --git a/exchange/targeting.go b/exchange/targeting.go
index f00e43d2..00008d29 100644
--- a/exchange/targeting.go
+++ b/exchange/targeting.go
@@ -35,7 +35,7 @@ type targetData struct {
 // The one exception is the `hb_cache_id` key. Since our APIs explicitly document cache keys to be on a "best effort" basis,
 // it's ok if those stay in the auction. For now, this method implements a very naive cache strategy.
 // In the future, we should implement a more clever retry & backoff strategy to balance the success rate & performance.
-func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMapping map[string]string) {
+func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMapping map[string]string, truncateTargetAttr *int) {
 	for impId, topBidsPerImp := range auc.winningBidsByBidder {
 		overallWinner := auc.winningBids[impId]
 		for bidderName, topBidPerBidder := range topBidsPerImp {
@@ -43,38 +43,38 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 
 			targets := make(map[string]string, 10)
 			if cpm, ok := auc.roundedPrices[topBidPerBidder]; ok {
-				targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbpbConstantKey, cpm, bidderName, isOverallWinner, truncateTargetAttr)
 			}
-			targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(bidderName), bidderName, isOverallWinner)
+			targData.addKeys(targets, openrtb_ext.HbBidderConstantKey, string(bidderName), bidderName, isOverallWinner, truncateTargetAttr)
 			if hbSize := makeHbSize(topBidPerBidder.bid); hbSize != "" {
-				targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbSizeConstantKey, hbSize, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if cacheID, ok := auc.cacheIds[topBidPerBidder.bid]; ok {
-				targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbCacheKey, cacheID, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if vastID, ok := auc.vastCacheIds[topBidPerBidder.bid]; ok {
-				targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbVastCacheKey, vastID, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if targData.includeFormat {
-				targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.bidType), bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbFormatKey, string(topBidPerBidder.bidType), bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if targData.cacheHost != "" {
-				targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbConstantCacheHostKey, targData.cacheHost, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if targData.cachePath != "" {
-				targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbConstantCachePathKey, targData.cachePath, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if deal := topBidPerBidder.bid.DealID; len(deal) > 0 {
-				targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, deal, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbDealIDConstantKey, deal, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			if isApp {
-				targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbEnvKey, openrtb_ext.HbEnvKeyApp, bidderName, isOverallWinner, truncateTargetAttr)
 			}
 			if len(categoryMapping) > 0 {
-				targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBidPerBidder.bid.ID], bidderName, isOverallWinner)
+				targData.addKeys(targets, openrtb_ext.HbCategoryDurationKey, categoryMapping[topBidPerBidder.bid.ID], bidderName, isOverallWinner, truncateTargetAttr)
 			}
 
 			topBidPerBidder.bidTargets = targets
@@ -82,9 +82,18 @@ func (targData *targetData) setTargeting(auc *auction, isApp bool, categoryMappi
 	}
 }
 
-func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool) {
+func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.TargetingKey, value string, bidderName openrtb_ext.BidderName, overallWinner bool, truncateTargetAttr *int) {
+	var maxLength int
+	if truncateTargetAttr != nil {
+		maxLength = *truncateTargetAttr
+		if maxLength < 0 {
+			maxLength = MaxKeyLength
+		}
+	} else {
+		maxLength = MaxKeyLength
+	}
 	if targData.includeBidderKeys {
-		keys[key.BidderKey(bidderName, MaxKeyLength)] = value
+		keys[key.BidderKey(bidderName, maxLength)] = value
 	}
 	if targData.includeWinners && overallWinner {
 		keys[string(key)] = value
diff --git a/go.mod b/go.mod
index cbe88128..10c5c5de 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ go 1.16
 require (
 	github.com/DATA-DOG/go-sqlmock v1.5.0
 	github.com/NYTimes/gziphandler v1.1.1
-	github.com/OneOfOne/xxhash v1.2.8 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
 	github.com/buger/jsonparser v1.1.1
 	github.com/chasex/glog v0.0.0-20160217080310-c62392af379c
@@ -13,10 +12,8 @@ require (
 	github.com/docker/go-units v0.4.0
 	github.com/gofrs/uuid v4.2.0+incompatible
 	github.com/golang/glog v1.0.0
-	github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // indirect
 	github.com/julienschmidt/httprouter v1.3.0
-	github.com/kr/text v0.2.0 // indirect
-	github.com/lib/pq v1.0.0
+	github.com/lib/pq v1.10.4
 	github.com/mitchellh/copystructure v1.2.0
 	github.com/mxmCherry/openrtb/v15 v15.0.1
 	github.com/prebid/go-gdpr v1.11.0
@@ -24,19 +21,16 @@ require (
 	github.com/prometheus/client_model v0.2.0
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
 	github.com/rs/cors v1.8.2
-	github.com/sergi/go-diff v1.0.0 // indirect
-	github.com/spaolacci/murmur3 v1.1.0 // indirect
+	github.com/sergi/go-diff v1.2.0 // indirect
 	github.com/spf13/viper v1.10.1
-	github.com/stretchr/objx v0.3.0 // indirect
 	github.com/stretchr/testify v1.7.0
 	github.com/vrischmann/go-metrics-influxdb v0.1.1
 	github.com/xeipuuv/gojsonschema v1.2.0
 	github.com/yudai/gojsondiff v1.0.0
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	github.com/yudai/pp v2.0.1+incompatible // indirect
-	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
+	golang.org/x/net v0.0.0-20220225172249-27dd8689420f
 	golang.org/x/text v0.3.7
-	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 )
diff --git a/go.sum b/go.sum
index a5d98304..791e36b4 100644
--- a/go.sum
+++ b/go.sum
@@ -52,9 +52,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
 github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
+github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
-github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -104,7 +103,6 @@ github.com/coocood/freecache v1.2.0 h1:p8RhjN6Y4DRBIMzdRlm1y+M7h7YJxye3lGW8/VvzC
 github.com/coocood/freecache v1.2.0/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -253,9 +251,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
-github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig=
-github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -274,15 +271,13 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
 github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@@ -384,14 +379,13 @@ github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
-github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
-github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
 github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
 github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
@@ -404,9 +398,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
 github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
 github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
-github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -539,8 +532,8 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -866,9 +859,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
 gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 9d16143f..b70907ea 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -300,6 +300,8 @@ const (
 	CookieSyncBadRequest            CookieSyncStatus = "bad_request"
 	CookieSyncOptOut                CookieSyncStatus = "opt_out"
 	CookieSyncGDPRHostCookieBlocked CookieSyncStatus = "gdpr_blocked_host_cookie"
+	CookieSyncAccountBlocked        CookieSyncStatus = "acct_blocked"
+	CookieSyncAccountInvalid        CookieSyncStatus = "acct_invalid"
 )
 
 // CookieSyncStatuses returns possible cookie sync statuses.
@@ -404,7 +406,7 @@ type MetricsEngine interface {
 	RecordStoredDataError(labels StoredDataLabels)
 	RecordPrebidCacheRequestTime(success bool, length time.Duration)
 	RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration)
-	RecordTimeoutNotice(sucess bool)
+	RecordTimeoutNotice(success bool)
 	RecordRequestPrivacy(privacy PrivacyLabels)
 	RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName)
 }
diff --git a/openrtb_ext/imp_adkernelAdn.go b/openrtb_ext/imp_adkernelAdn.go
index 803f3fa4..fee22318 100644
--- a/openrtb_ext/imp_adkernelAdn.go
+++ b/openrtb_ext/imp_adkernelAdn.go
@@ -2,6 +2,5 @@ package openrtb_ext
 
 // ExtImpAdkernelAdn defines the contract for bidrequest.imp[i].ext.adkernelAdn
 type ExtImpAdkernelAdn struct {
-	PublisherID int    `json:"pubId"`
-	Host        string `json:"host,omitempty"`
+	PublisherID int `json:"pubId"`
 }
diff --git a/openrtb_ext/imp_pubmatic.go b/openrtb_ext/imp_pubmatic.go
index cf5093a2..db96ca51 100644
--- a/openrtb_ext/imp_pubmatic.go
+++ b/openrtb_ext/imp_pubmatic.go
@@ -15,6 +15,7 @@ type ExtImpPubmatic struct {
 	PmZoneID    string                  `json:"pmzoneid"`
 	WrapExt     json.RawMessage         `json:"wrapper,omitempty"`
 	Keywords    []*ExtImpPubmaticKeyVal `json:"keywords,omitempty"`
+	Kadfloor    string                  `json:"kadfloor,omitempty"`
 }
 
 // ExtImpPubmaticKeyVal defines the contract for bidrequest.imp[i].ext.pubmatic.keywords[i]
diff --git a/router/router.go b/router/router.go
index e49f88b1..eb887c02 100644
--- a/router/router.go
+++ b/router/router.go
@@ -235,7 +235,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
 	r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(bidderInfos, defaultAliases))
 	r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(bidderInfos, cfg.Adapters, defaultAliases))
 	r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases))
-	r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders).Handle)
+	r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncersByBidder, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, accounts, activeBidders).Handle)
 	r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse))
 	r.GET("/", serveIndex)
 	r.Handler("GET", "/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev))
diff --git a/static/bidder-params/adkernelAdn.json b/static/bidder-params/adkernelAdn.json
index 6c1afe11..84448245 100644
--- a/static/bidder-params/adkernelAdn.json
+++ b/static/bidder-params/adkernelAdn.json
@@ -9,10 +9,6 @@
         "type": "integer",
         "minimum": 1,
         "description": "Publisher Id to use."
-      },
-      "host": {
-        "type": "string",
-        "description": "Network host to send request"
       }
   },
   "required": ["pubId"]
diff --git a/static/bidder-params/pubmatic.json b/static/bidder-params/pubmatic.json
index 5d41b9fc..a13b5906 100644
--- a/static/bidder-params/pubmatic.json
+++ b/static/bidder-params/pubmatic.json
@@ -16,6 +16,10 @@
 			"type": "string",
 			"description": "Comma separated zone id. Used im deal targeting & site section targeting. e.g drama,sport"
 		},
+		"kadfloor": {
+			"type": "string",
+			"description": "bid floor value set to imp.bidfloor"
+		},
 		"dctr": {
 			"type": "string",
 			"description": "Deals Custom Targeting, pipe separated key-value pairs e.g key1=V1,V2,V3|key2=v1|key3=v3,v5"


Test patch:
diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go
index 687f5a94..945a415f 100644
--- a/adapters/adkernelAdn/adkernelAdn_test.go
+++ b/adapters/adkernelAdn/adkernelAdn_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderAdkernelAdn, config.Adapter{
-		Endpoint: "http://{{.Host}}/rtbpub?account={{.PublisherID}}"})
+		Endpoint: "https://pbs2.adksrv.com/rtbpub?account={{.PublisherID}}"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json
index 71df95b7..2a6db14d 100644
--- a/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json
+++ b/adapters/adkernelAdn/adkerneladntest/exemplary/multiformat-impression.json
@@ -36,7 +36,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=102",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=102",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json
index 1b5472df..6df3e0f1 100644
--- a/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json
+++ b/adapters/adkernelAdn/adkerneladntest/exemplary/single-banner-impression.json
@@ -29,7 +29,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json b/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json
index 848d0c3d..e60aa06c 100644
--- a/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json
+++ b/adapters/adkernelAdn/adkerneladntest/exemplary/single-video-impression.json
@@ -33,7 +33,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=102",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=102",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json b/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json
index 950a6c28..ae71ff41 100644
--- a/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json
+++ b/adapters/adkernelAdn/adkerneladntest/supplemental/204status.json
@@ -26,7 +26,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json b/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json
index a7b3e251..8a25aae0 100644
--- a/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json
+++ b/adapters/adkernelAdn/adkerneladntest/supplemental/http-err-status.json
@@ -26,7 +26,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
           "id": "0000000000001",
           "imp": [
diff --git a/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json b/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json
index 59296dc6..37a5a36d 100644
--- a/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json
+++ b/adapters/adkernelAdn/adkerneladntest/supplemental/two-impressions-two-seatbids.json
@@ -37,7 +37,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://tag.test.com/rtbpub?account=101",
+        "uri": "https://pbs2.adksrv.com/rtbpub?account=101",
         "body": {
             "app": {
             "bundle": "com.rovia.angrybirds",
diff --git a/adapters/infoawarebidder_test.go b/adapters/infoawarebidder_test.go
index 62bcc08d..7bd476b3 100644
--- a/adapters/infoawarebidder_test.go
+++ b/adapters/infoawarebidder_test.go
@@ -173,7 +173,7 @@ func TestImpFiltering(t *testing.T) {
 		}
 
 		// 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.Len(t, actualAdapterRequests, test.expectedImpLen, "Test failed. Incorrect length of filtered imps: %s", test.description)
 	}
 }
 
diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go
index ac7dbdb7..da5f632e 100644
--- a/adapters/pubmatic/pubmatic_test.go
+++ b/adapters/pubmatic/pubmatic_test.go
@@ -1,11 +1,14 @@
 package pubmatic
 
 import (
+	"encoding/json"
 	"testing"
 
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
 	"github.com/prebid/prebid-server/adapters/adapterstest"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestJsonSamples(t *testing.T) {
@@ -67,3 +70,83 @@ func TestGetBidTypeForUnsupportedCode(t *testing.T) {
 		t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue)
 	}
 }
+
+func TestParseImpressionObject(t *testing.T) {
+	type args struct {
+		imp                      *openrtb2.Imp
+		extractWrapperExtFromImp bool
+		extractPubIDFromImp      bool
+	}
+	tests := []struct {
+		name                string
+		args                args
+		expectedWrapperExt  *pubmaticWrapperExt
+		expectedPublisherId string
+		wantErr             bool
+		expectedBidfloor    float64
+	}{
+		{
+			name: "imp.bidfloor empty and kadfloor set",
+			args: args{
+				imp: &openrtb2.Imp{
+					Video: &openrtb2.Video{},
+					Ext:   json.RawMessage(`{"bidder":{"kadfloor":"0.12"}}`),
+				},
+			},
+			expectedBidfloor: 0.12,
+		},
+		{
+			name: "imp.bidfloor set and kadfloor empty",
+			args: args{
+				imp: &openrtb2.Imp{
+					BidFloor: 0.12,
+					Video:    &openrtb2.Video{},
+					Ext:      json.RawMessage(`{"bidder":{}}`),
+				},
+			},
+			expectedBidfloor: 0.12,
+		},
+		{
+			name: "imp.bidfloor set and kadfloor invalid",
+			args: args{
+				imp: &openrtb2.Imp{
+					BidFloor: 0.12,
+					Video:    &openrtb2.Video{},
+					Ext:      json.RawMessage(`{"bidder":{"kadfloor":"aaa"}}`),
+				},
+			},
+			expectedBidfloor: 0.12,
+		},
+		{
+			name: "imp.bidfloor set and kadfloor set, preference to kadfloor",
+			args: args{
+				imp: &openrtb2.Imp{
+					BidFloor: 0.12,
+					Video:    &openrtb2.Video{},
+					Ext:      json.RawMessage(`{"bidder":{"kadfloor":"0.11"}}`),
+				},
+			},
+			expectedBidfloor: 0.11,
+		},
+		{
+			name: "kadfloor string set with whitespace",
+			args: args{
+				imp: &openrtb2.Imp{
+					BidFloor: 0.12,
+					Video:    &openrtb2.Video{},
+					Ext:      json.RawMessage(`{"bidder":{"kadfloor":" \t  0.13  "}}`),
+				},
+			},
+			expectedBidfloor: 0.13,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			receivedWrapperExt, receivedPublisherId, err := parseImpressionObject(tt.args.imp, tt.args.extractWrapperExtFromImp, tt.args.extractPubIDFromImp)
+			assert.Equal(t, tt.wantErr, err != nil)
+			assert.Equal(t, tt.expectedWrapperExt, receivedWrapperExt)
+			assert.Equal(t, tt.expectedPublisherId, receivedPublisherId)
+			assert.Equal(t, tt.expectedBidfloor, tt.args.imp.BidFloor)
+		})
+	}
+}
diff --git a/adapters/pubmatic/pubmatictest/exemplary/banner.json b/adapters/pubmatic/pubmatictest/exemplary/banner.json
index f525673c..313099f2 100644
--- a/adapters/pubmatic/pubmatictest/exemplary/banner.json
+++ b/adapters/pubmatic/pubmatictest/exemplary/banner.json
@@ -22,6 +22,7 @@
                          "value": ["sports", "movies"]
                       }
                     ],
+                    "kadfloor": "0.12",
                     "wrapper": {
                         "version": 1,
                         "profile": 5123
@@ -56,10 +57,11 @@
                       "w": 300,
                       "h": 250
                     }
-                ],
-                "h": 250,
-                "w": 300
-            },
+                  ],
+                  "h": 250,
+                  "w": 300
+                },
+                "bidfloor": 0.12,
                 "ext": {
                     "pmZoneId": "Zone1,Zone2",
                     "preference": "sports,movies"
diff --git a/adapters/pubmatic/pubmatictest/exemplary/video.json b/adapters/pubmatic/pubmatictest/exemplary/video.json
index 1715e477..693ee3eb 100644
--- a/adapters/pubmatic/pubmatictest/exemplary/video.json
+++ b/adapters/pubmatic/pubmatictest/exemplary/video.json
@@ -28,6 +28,7 @@
                         "value": ["Zone1", "Zone2"]
                       }
                     ],
+                    "kadfloor": "0.12",
                     "wrapper": {
                         "version": 1,
                         "profile": 5123
@@ -72,6 +73,7 @@
                     "minbitrate": 10,
                     "maxbitrate": 10
                 },
+                "bidfloor": 0.12,
                 "ext": {
                     "pmZoneId": "Zone1,Zone2"
                 }
diff --git a/adapters/pubmatic/pubmatictest/supplemental/app.json b/adapters/pubmatic/pubmatictest/supplemental/app.json
index e9f1048e..c6ed97a5 100644
--- a/adapters/pubmatic/pubmatictest/supplemental/app.json
+++ b/adapters/pubmatic/pubmatictest/supplemental/app.json
@@ -22,6 +22,7 @@
                             "value": ["sports", "movies"]
                         }
                     ],
+                    "kadfloor": "0.12",
                     "wrapper": {
                         "version": 1,
                         "profile": 5123
@@ -56,10 +57,11 @@
                       "w": 300,
                       "h": 250
                     }
-                ],
-                "h": 250,
-                "w": 300
-            },
+                  ],
+                  "h": 250,
+                  "w": 300
+                },
+                "bidfloor": 0.12,
                 "ext": {
                     "pmZoneId": "Zone1,Zone2",
                     "preference": "sports,movies"
diff --git a/adapters/telaria/telariatest/exemplary/multiple-video-web.json b/adapters/telaria/telariatest/exemplary/multiple-video-web.json
new file mode 100644
index 00000000..e28d0cfa
--- /dev/null
+++ b/adapters/telaria/telariatest/exemplary/multiple-video-web.json
@@ -0,0 +1,245 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [
+      {
+        "id": "0_0",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150,
+          "ext": {
+            "podduration": 30,
+            "maxseq": 2
+          }
+        },
+        "ext": {
+          "bidder": {
+            "adCode": "my-adcode",
+            "seatCode": "my-seatcode",
+            "extra": {
+              "custom": "1234"
+            }
+          }
+        }
+      },
+      {
+        "id": "0_1",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "adCode": "my-adcode",
+            "seatCode": "my-seatcode",
+            "extra": {
+              "custom": "1234"
+            }
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Dnt": [
+            "0"
+          ]
+        },
+        "uri": "https://ads.tremorhub.com/ad/rtb/prebid",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "0_0",
+              "tagid": "my-adcode",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 120,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480,
+                "ext": {
+                  "podduration": 30,
+                  "maxseq": 2
+                }
+              },
+              "ext": {
+                "originalTagid": "ogTAGID",
+                "originalPublisherid": "123456789"
+              }
+            }
+          ],
+          "site": {
+            "page": "test.com",
+            "publisher": {
+              "id": "my-seatcode"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000,
+          "ext": {
+            "extra": {
+              "custom": "1234"
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "0_0",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 1280,
+                  "h": 720,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                },
+                {
+                  "id": "25c6976b14e041029afd5b00acabd933",
+                  "impid": "0_0",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 1280,
+                  "h": 720,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                }
+              ],
+              "seat": "telaria"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "telaria": 154
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+          "impid": "0_0",
+          "price": 3.5,
+          "adm": "awesome-markup",
+          "adomain": [
+            "awesome.com"
+          ],
+          "crid": "20",
+          "w": 1280,
+          "h": 720,
+          "ext": {
+            "prebid": {
+              "type": "video"
+            }
+          }
+        },
+        "type": "video"
+      },{
+        "bid": {
+          "id": "25c6976b14e041029afd5b00acabd933",
+          "impid": "0_1",
+          "price": 3.5,
+          "adm": "awesome-markup",
+          "adomain": [
+            "awesome.com"
+          ],
+          "crid": "20",
+          "w": 1280,
+          "h": 720,
+          "ext": {
+            "prebid": {
+              "type": "video"
+            }
+          }
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json b/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json
new file mode 100644
index 00000000..49bdb6b2
--- /dev/null
+++ b/adapters/telaria/telariatest/exemplary/multiple-vidoe-app.json
@@ -0,0 +1,257 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "0_0",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150,
+          "ext": {
+            "podduration": 30,
+            "maxseq": 2
+          }
+        },
+        "ext": {
+          "bidder": {
+            "adCode": "my-adcode",
+            "seatCode": "my-seatcode",
+            "extra": {
+              "custom": "1234"
+            }
+          }
+        }
+      },
+      {
+        "id": "0_1",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "adCode": "my-adcode",
+            "seatCode": "my-seatcode",
+            "extra": {
+              "custom": "1234"
+            }
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Dnt": [
+            "0"
+          ]
+        },
+        "uri": "https://ads.tremorhub.com/ad/rtb/prebid",
+        "body": {
+          "id": "some-request-id",
+          "ext": {
+            "extra": {
+              "custom": "1234"
+            }
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "0_0",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 120,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480,
+                "ext": {
+                  "podduration": 30,
+                  "maxseq": 2
+                }
+              },
+              "tagid": "my-adcode",
+              "ext": {
+                "originalTagid": "ogTAGID",
+                "originalPublisherid": "123456789"
+              }
+            }
+          ],
+          "app": {
+            "id": "123456789",
+            "name": "Awesome App",
+            "bundle": "com.app.awesome",
+            "domain": "awesomeapp.com",
+            "cat": [
+              "IAB22-1"
+            ],
+            "publisher": {
+              "id": "my-seatcode"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "0_0",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 1280,
+                  "h": 720,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                },
+                {
+                  "id": "25c6976b14e041029afd5b00acabd933",
+                  "impid": "0_0",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 1280,
+                  "h": 720,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                }
+              ],
+              "seat": "telaria"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "telaria": 154
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+          "impid": "0_0",
+          "price": 3.5,
+          "adm": "awesome-markup",
+          "adomain": [
+            "awesome.com"
+          ],
+          "crid": "20",
+          "w": 1280,
+          "h": 720,
+          "ext": {
+            "prebid": {
+              "type": "video"
+            }
+          }
+        },
+        "type": "video"
+      },{
+        "bid": {
+          "id": "25c6976b14e041029afd5b00acabd933",
+          "impid": "0_1",
+          "price": 3.5,
+          "adm": "awesome-markup",
+          "adomain": [
+            "awesome.com"
+          ],
+          "crid": "20",
+          "w": 1280,
+          "h": 720,
+          "ext": {
+            "prebid": {
+              "type": "video"
+            }
+          }
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/telaria/telariatest/exemplary/video-app.json b/adapters/telaria/telariatest/exemplary/video-app.json
index 24065693..4041b01d 100644
--- a/adapters/telaria/telariatest/exemplary/video-app.json
+++ b/adapters/telaria/telariatest/exemplary/video-app.json
@@ -134,7 +134,7 @@
               "bid": [
                 {
                   "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "1",
+                  "impid": "some-impression-id",
                   "price": 3.5,
                   "adm": "awesome-markup",
                   "adomain": [
@@ -169,7 +169,7 @@
       "bids": [{
         "bid": {
           "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-          "impid": "1",
+          "impid": "some-impression-id",
           "price": 3.5,
           "adm": "awesome-markup",
           "adomain": [
diff --git a/adapters/telaria/telariatest/exemplary/video-web.json b/adapters/telaria/telariatest/exemplary/video-web.json
index bfa72c9c..1ce1d3ad 100644
--- a/adapters/telaria/telariatest/exemplary/video-web.json
+++ b/adapters/telaria/telariatest/exemplary/video-web.json
@@ -122,7 +122,7 @@
               "bid": [
                 {
                   "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-                  "impid": "1",
+                  "impid": "some-impression-id",
                   "price": 3.5,
                   "adm": "awesome-markup",
                   "adomain": [
@@ -157,7 +157,7 @@
       "bids": [{
         "bid": {
           "id": "a3ae1b4e2fc24a4fb45540082e98e161",
-          "impid": "1",
+          "impid": "some-impression-id",
           "price": 3.5,
           "adm": "awesome-markup",
           "adomain": [
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index 8cbba3ec..263f8b1f 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -2,6 +2,7 @@ package endpoints
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"io"
 	"net/http"
@@ -12,6 +13,7 @@ import (
 
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -34,6 +36,7 @@ func TestNewCookieSyncEndpoint(t *testing.T) {
 		configCCPAEnforce = true
 		metrics           = metrics.MetricsEngineMock{}
 		analytics         = MockAnalytics{}
+		fetcher           = FakeAccountsFetcher{}
 		bidders           = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")}
 	)
 
@@ -48,21 +51,27 @@ func TestNewCookieSyncEndpoint(t *testing.T) {
 		&gdprPerms,
 		&metrics,
 		&analytics,
+		&fetcher,
 		bidders,
 	)
 
 	expected := &cookieSyncEndpoint{
-		chooser:          usersync.NewChooser(syncersByBidder),
-		config:           configUserSync,
-		hostCookieConfig: &configHostCookie,
+		chooser: usersync.NewChooser(syncersByBidder),
+		config: &config.Configuration{
+			UserSync:   configUserSync,
+			HostCookie: configHostCookie,
+			GDPR:       configGDPR,
+			CCPA:       config.CCPA{Enforce: configCCPAEnforce},
+		},
 		privacyConfig: usersyncPrivacyConfig{
 			gdprConfig:      configGDPR,
 			gdprPermissions: &gdprPerms,
 			ccpaEnforce:     configCCPAEnforce,
 			bidderHashSet:   map[string]struct{}{"bidderA": {}, "bidderB": {}},
 		},
-		metrics:      &metrics,
-		pbsAnalytics: &analytics,
+		metrics:         &metrics,
+		pbsAnalytics:    &analytics,
+		accountsFetcher: &fetcher,
 	}
 
 	assert.Equal(t, expected, endpoint)
@@ -238,8 +247,8 @@ func TestCookieSyncHandle(t *testing.T) {
 		writer := httptest.NewRecorder()
 
 		endpoint := cookieSyncEndpoint{
-			chooser:          FakeChooser{Result: test.givenChooserResult},
-			hostCookieConfig: &config.HostCookie{},
+			chooser: FakeChooser{Result: test.givenChooserResult},
+			config:  &config.Configuration{},
 			privacyConfig: usersyncPrivacyConfig{
 				gdprConfig: config.GDPR{
 					Enabled:      true,
@@ -263,14 +272,15 @@ func TestCookieSyncParseRequest(t *testing.T) {
 	expectedCCPAParsedPolicy, _ := ccpa.Policy{Consent: "1NYN"}.Parse(map[string]struct{}{})
 
 	testCases := []struct {
-		description      string
-		givenConfig      config.UserSync
-		givenBody        io.Reader
-		givenGDPRConfig  config.GDPR
-		givenCCPAEnabled bool
-		expectedError    string
-		expectedPrivacy  privacy.Policies
-		expectedRequest  usersync.Request
+		description          string
+		givenConfig          config.UserSync
+		givenBody            io.Reader
+		givenGDPRConfig      config.GDPR
+		givenCCPAEnabled     bool
+		givenAccountRequired bool
+		expectedError        string
+		expectedPrivacy      privacy.Policies
+		expectedRequest      usersync.Request
 	}{
 		{
 			description: "Complete Request",
@@ -647,18 +657,121 @@ func TestCookieSyncParseRequest(t *testing.T) {
 			givenCCPAEnabled: true,
 			expectedError:    "Failed to read request body",
 		},
+		{
+			description: "Account Defaults - Max Limit + Default Coop",
+			givenBody: strings.NewReader(`{` +
+				`"bidders":["a", "b"],` +
+				`"limit":42,` +
+				`"account":"TestAccount"` +
+				`}`),
+			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
+			givenCCPAEnabled: true,
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedPrivacy: privacy.Policies{},
+			expectedRequest: usersync.Request{
+				Bidders: []string{"a", "b"},
+				Cooperative: usersync.Cooperative{
+					Enabled:        true,
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+				Limit: 30,
+				Privacy: usersyncPrivacy{
+					gdprSignal: gdpr.SignalAmbiguous,
+				},
+				SyncTypeFilter: usersync.SyncTypeFilter{
+					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+				},
+			},
+		},
+		{
+			description: "Account Defaults - DefaultLimit",
+			givenBody: strings.NewReader(`{` +
+				`"bidders":["a", "b"],` +
+				`"account":"TestAccount"` +
+				`}`),
+			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
+			givenCCPAEnabled: true,
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					EnabledByDefault: false,
+					PriorityGroups:   [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedPrivacy: privacy.Policies{},
+			expectedRequest: usersync.Request{
+				Bidders: []string{"a", "b"},
+				Cooperative: usersync.Cooperative{
+					Enabled:        true,
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+				Limit: 20,
+				Privacy: usersyncPrivacy{
+					gdprSignal: gdpr.SignalAmbiguous,
+				},
+				SyncTypeFilter: usersync.SyncTypeFilter{
+					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+				},
+			},
+		},
+		{
+			description: "Account Defaults - Error",
+			givenBody: strings.NewReader(`{` +
+				`"bidders":["a", "b"],` +
+				`"account":"DisabledAccount"` +
+				`}`),
+			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
+			givenCCPAEnabled: true,
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					EnabledByDefault: false,
+					PriorityGroups:   [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedPrivacy: privacy.Policies{},
+			expectedRequest: usersync.Request{
+				Bidders: []string{"a", "b"},
+				Cooperative: usersync.Cooperative{
+					Enabled:        true,
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+				Limit: 20,
+				Privacy: usersyncPrivacy{
+					gdprSignal: gdpr.SignalAmbiguous,
+				},
+				SyncTypeFilter: usersync.SyncTypeFilter{
+					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
+				},
+			},
+			expectedError:        errCookieSyncAccountBlocked.Error(),
+			givenAccountRequired: true,
+		},
 	}
 
 	for _, test := range testCases {
 		httpRequest := httptest.NewRequest("POST", "/cookiesync", test.givenBody)
 
 		endpoint := cookieSyncEndpoint{
-			config: test.givenConfig,
+			config: &config.Configuration{
+				UserSync:        test.givenConfig,
+				AccountRequired: test.givenAccountRequired,
+			},
 			privacyConfig: usersyncPrivacyConfig{
 				gdprConfig:  test.givenGDPRConfig,
 				ccpaEnforce: test.givenCCPAEnabled,
 			},
+			accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{
+				"TestAccount":     json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`),
+				"DisabledAccount": json.RawMessage(`{"disabled":true}`),
+			}},
 		}
+		assert.NoError(t, endpoint.config.MarshalAccountDefaults())
 		request, privacyPolicies, err := endpoint.parseRequest(httpRequest)
 
 		if test.expectedError == "" {
@@ -673,6 +786,185 @@ func TestCookieSyncParseRequest(t *testing.T) {
 	}
 }
 
+func TestSetLimit(t *testing.T) {
+	coopSync := true
+
+	testCases := []struct {
+		description     string
+		givenRequest    cookieSyncRequest
+		givenConfig     config.UserSync
+		expectedError   string
+		expectedRequest cookieSyncRequest
+	}{
+		{
+			description: "Test that Max Limit and Default Coop Sync are Applied",
+			givenRequest: cookieSyncRequest{
+				Account: "TestAccount",
+				Limit:   42,
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "TestAccount",
+				Limit:           30,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Test that Default Limit is Applied",
+			givenRequest: cookieSyncRequest{
+				Account: "TestAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "TestAccount",
+				Limit:           20,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Test that max limit is applied when given limit is zero",
+			givenRequest: cookieSyncRequest{
+				Account: "ZeroLimitAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "ZeroLimitAccount",
+				Limit:           30,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Negative Value Check",
+			givenRequest: cookieSyncRequest{
+				Account: "NegativeLimitAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account:         "NegativeLimitAccount",
+				Limit:           0,
+				CooperativeSync: &coopSync,
+			},
+			expectedError: "",
+		},
+		{
+			description: "Error Handler",
+			givenRequest: cookieSyncRequest{
+				Account: "DisabledAccount",
+			},
+			givenConfig: config.UserSync{
+				Cooperative: config.UserSyncCooperative{
+					PriorityGroups: [][]string{{"a", "b", "c"}},
+				},
+			},
+			expectedRequest: cookieSyncRequest{
+				Account: "DisabledAccount",
+			},
+			expectedError: errCookieSyncAccountBlocked.Error(),
+		},
+	}
+
+	for _, test := range testCases {
+		endpoint := cookieSyncEndpoint{
+			config: &config.Configuration{
+				UserSync: test.givenConfig,
+			},
+			accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{
+				"TestAccount":          json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`),
+				"DisabledAccount":      json.RawMessage(`{"disabled":true}`),
+				"ZeroLimitAccount":     json.RawMessage(`{"cookie_sync": {"default_limit": 0, "max_limit": 30, "default_coop_sync": true}}`),
+				"NegativeLimitAccount": json.RawMessage(`{"cookie_sync": {"default_limit": -1, "max_limit": -1, "default_coop_sync": true}}`),
+			}},
+		}
+		assert.NoError(t, endpoint.config.MarshalAccountDefaults())
+		request, err := endpoint.setLimit(test.givenRequest)
+
+		if test.expectedError == "" {
+			assert.NoError(t, err, test.description)
+			assert.Equal(t, test.expectedRequest, request, test.description)
+		} else {
+			assert.EqualError(t, err, test.expectedError, test.description)
+		}
+	}
+}
+
+func TestWriteParseRequestErrorMetrics(t *testing.T) {
+	err := errors.New("anyError")
+
+	mockAnalytics := MockAnalytics{}
+	mockAnalytics.On("LogCookieSyncObject", mock.Anything)
+	writer := httptest.NewRecorder()
+
+	endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics}
+	endpoint.handleError(writer, err, 418)
+
+	assert.Equal(t, writer.Code, 418)
+	assert.Equal(t, writer.Body.String(), "anyError\n")
+	mockAnalytics.AssertCalled(t, "LogCookieSyncObject", &analytics.CookieSyncObject{
+		Status:       418,
+		Errors:       []error{err},
+		BidderStatus: []*analytics.CookieSyncBidder{},
+	})
+}
+
+func TestCookieSyncWriteParseRequestErrorMetrics(t *testing.T) {
+	testCases := []struct {
+		description     string
+		err             error
+		setExpectations func(*metrics.MetricsEngineMock)
+	}{
+		{
+			description: "Account Blocked",
+			err:         errCookieSyncAccountBlocked,
+			setExpectations: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordCookieSync", metrics.CookieSyncAccountBlocked).Once()
+			},
+		},
+		{
+			description: "Account Invalid",
+			err:         errCookieSyncAccountInvalid,
+			setExpectations: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordCookieSync", metrics.CookieSyncAccountInvalid).Once()
+			},
+		},
+		{
+			description: "No Special Case",
+			err:         errors.New("any error"),
+			setExpectations: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once()
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		mockMetrics := metrics.MetricsEngineMock{}
+		test.setExpectations(&mockMetrics)
+
+		endpoint := &cookieSyncEndpoint{metrics: &mockMetrics}
+		endpoint.writeParseRequestErrorMetrics(test.err)
+
+		mockMetrics.AssertExpectations(t)
+	}
+}
+
 func TestParseTypeFilter(t *testing.T) {
 	testCases := []struct {
 		description    string
@@ -1242,6 +1534,50 @@ func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) {
 	}
 }
 
+func TestCombineErrors(t *testing.T) {
+	testCases := []struct {
+		description    string
+		givenErrorList []error
+		expectedError  error
+	}{
+		{
+			description:    "No errors given",
+			givenErrorList: []error{},
+			expectedError:  errors.New(""),
+		},
+		{
+			description:    "One error given",
+			givenErrorList: []error{errors.New("Error #1")},
+			expectedError:  errors.New("Error #1"),
+		},
+		{
+			description:    "Multiple errors given",
+			givenErrorList: []error{errors.New("Error #1"), errors.New("Error #2")},
+			expectedError:  errors.New("Error #1 Error #2"),
+		},
+		{
+			description:    "Special Case: blocked (rejected via block list)",
+			givenErrorList: []error{&errortypes.BlacklistedAcct{}},
+			expectedError:  errCookieSyncAccountBlocked,
+		},
+		{
+			description:    "Special Case: invalid (rejected via allow list)",
+			givenErrorList: []error{&errortypes.AcctRequired{}},
+			expectedError:  errCookieSyncAccountInvalid,
+		},
+		{
+			description:    "Special Case: multiple special cases, first one wins",
+			givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}},
+			expectedError:  errCookieSyncAccountBlocked,
+		},
+	}
+
+	for _, test := range testCases {
+		combinedErrors := combineErrors(test.givenErrorList)
+		assert.Equal(t, test.expectedError, combinedErrors, test.description)
+	}
+}
+
 type FakeChooser struct {
 	Result usersync.Result
 }
@@ -1321,6 +1657,17 @@ func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCore
 	return args.Bool(0), args.Bool(1), args.Bool(2), args.Error(3)
 }
 
+type FakeAccountsFetcher struct {
+	AccountData map[string]json.RawMessage
+}
+
+func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, accountID string) (json.RawMessage, []error) {
+	if account, ok := f.AccountData[accountID]; ok {
+		return account, nil
+	}
+	return nil, []error{errors.New("Account not found")}
+}
+
 // ErrReader returns an io.Reader that returns 0, err from all Read calls. This is added in
 // Go 1.16. Copied here for now until we switch over.
 func ErrReader(err error) io.Reader {
diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go
index 09ad6d57..f700c31e 100644
--- a/endpoints/events/event_test.go
+++ b/endpoints/events/event_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/stretchr/testify/assert"
 )
@@ -645,6 +646,16 @@ func TestShouldParseEventCorrectly(t *testing.T) {
 				Analytics: analytics.Enabled,
 			},
 		},
+		"three - vtype = start": {
+			req: httptest.NewRequest("GET", "/event?t=vast&vtype=start&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			expected: &analytics.EventRequest{
+				Type:      analytics.Vast,
+				VType:     analytics.Start,
+				BidID:     "bidId",
+				Timestamp: 0,
+				Analytics: analytics.Enabled,
+			},
+		},
 	}
 
 	for name, test := range tests {
@@ -750,3 +761,147 @@ func TestReadIntegrationType(t *testing.T) {
 		}
 	}
 }
+
+func TestShouldReturnBadRequestWhenVTypeIsInvalid(t *testing.T) {
+
+	reqData := ""
+
+	tests := []struct {
+		description        string
+		req                *http.Request
+		expectedStatusCode int
+		expectedStatus     string
+	}{
+		{
+			description:        "vtype parameter is missing",
+			req:                httptest.NewRequest("GET", "/event?t=vast&b=bidID", strings.NewReader(reqData)),
+			expectedStatusCode: 400,
+			expectedStatus:     "invalid request: parameter 'vtype' is required\n",
+		},
+		{
+			description:        "invalid vtype parameter",
+			req:                httptest.NewRequest("GET", "/event?t=vast&vtype=abc&b=bidID", strings.NewReader(reqData)),
+			expectedStatusCode: 400,
+			expectedStatus:     "invalid request: unknown vtype: 'abc'\n",
+		},
+		{
+			description:        "vtype is passed when event != vast",
+			req:                httptest.NewRequest("GET", "/event?t=win&vtype=startc&b=bidID", strings.NewReader(reqData)),
+			expectedStatusCode: 400,
+			expectedStatus:     "invalid request: parameter 'vtype' is only required for t=vast\n",
+		},
+	}
+
+	for _, test := range tests {
+		mockAccountsFetcher := &mockAccountsFetcher{}
+
+		mockAnalyticsModule := &eventsMockAnalyticsModule{
+			Fail: false,
+		}
+
+		cfg := &config.Configuration{
+			AccountDefaults: config.Account{},
+		}
+
+		recorder := httptest.NewRecorder()
+
+		e := NewEventEndpoint(cfg, mockAccountsFetcher, mockAnalyticsModule)
+		e(recorder, test.req, nil)
+
+		d, err := ioutil.ReadAll(recorder.Result().Body)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assert.Equal(t, test.expectedStatusCode, recorder.Result().StatusCode, test.description)
+		assert.Equal(t, test.expectedStatus, string(d), test.description)
+
+	}
+}
+
+func TestReadVType(t *testing.T) {
+	type args struct {
+		er  *analytics.EventRequest
+		req *http.Request
+	}
+	tests := []struct {
+		name          string
+		args          args
+		expectedError error
+		expectedVType analytics.VastType
+	}{
+		{
+			name: "vtype = start",
+			args: args{
+				er: &analytics.EventRequest{
+					Type: analytics.Vast,
+				},
+				req: httptest.NewRequest("GET", "/event?t=vast&vtype=start&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			},
+			expectedError: nil,
+			expectedVType: analytics.Start,
+		},
+		{
+			name: "vtype = firstQuartile",
+			args: args{
+				er: &analytics.EventRequest{
+					Type: analytics.Vast,
+				},
+				req: httptest.NewRequest("GET", "/event?t=vast&vtype=firstQuartile&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			},
+			expectedError: nil,
+			expectedVType: analytics.FirstQuartile,
+		},
+		{
+			name: "vtype = midPoint",
+			args: args{
+				er: &analytics.EventRequest{
+					Type: analytics.Vast,
+				},
+				req: httptest.NewRequest("GET", "/event?t=vast&vtype=midPoint&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			},
+			expectedError: nil,
+			expectedVType: analytics.MidPoint,
+		},
+		{
+			name: "vtype = thirdQuartile",
+			args: args{
+				er: &analytics.EventRequest{
+					Type: analytics.Vast,
+				},
+				req: httptest.NewRequest("GET", "/event?t=vast&vtype=thirdQuartile&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			},
+			expectedError: nil,
+			expectedVType: analytics.ThirdQuartile,
+		},
+		{
+			name: "vtype = complete",
+			args: args{
+				er: &analytics.EventRequest{
+					Type: analytics.Vast,
+				},
+				req: httptest.NewRequest("GET", "/event?t=vast&vtype=complete&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			},
+			expectedError: nil,
+			expectedVType: analytics.Complete,
+		},
+		{
+			name: "unknown vtype",
+			args: args{
+				er: &analytics.EventRequest{
+					Type: analytics.Vast,
+				},
+				req: httptest.NewRequest("GET", "/event?t=vast&vtype=test&b=bidId&ts=0&a=accountId", strings.NewReader("")),
+			},
+			expectedError: &errortypes.BadInput{Message: "unknown vtype: 'test'"},
+			expectedVType: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := readVType(tt.args.er, tt.args.req)
+			assert.Equal(t, tt.expectedError, err, tt.name)
+			assert.Equal(t, tt.expectedVType, tt.args.er.VType, tt.name)
+		})
+	}
+}
diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go
index 7110e188..9be25f44 100644
--- a/endpoints/info/bidders_test.go
+++ b/endpoints/info/bidders_test.go
@@ -179,7 +179,7 @@ func TestBiddersHandler(t *testing.T) {
 		expectedHeaders http.Header
 	}{
 		{
-			description:     "No Query Paramters - Backwards Compatability",
+			description:     "No Query Parameters - Backwards Compatibility",
 			givenURL:        "/info/bidders",
 			expectedStatus:  http.StatusOK,
 			expectedBody:    `["a","b","x","y"]`,
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 48e054c7..f2726de0 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -247,6 +247,7 @@ type TargetingTestData struct {
 	IsApp                      bool
 	CategoryMapping            map[string]string
 	ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string
+	TruncateTargetAttr         *int
 }
 
 var bid123 *openrtb2.Bid = &openrtb2.Bid{
@@ -261,6 +262,9 @@ var bid084 *openrtb2.Bid = &openrtb2.Bid{
 	Price: 0.84,
 }
 
+var truncateTargetAttrValueLess int = 10
+var truncateTargetAttrValueGreater int = 25
+var truncateTargetAttrValueNegative int = -1
 var TargetingTests []TargetingTestData = []TargetingTestData{
 	{
 		Description: "Targeting winners only (most basic targeting example)",
@@ -291,6 +295,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				openrtb_ext.BidderRubicon: {},
 			},
 		},
+		TruncateTargetAttr: nil,
 	},
 	{
 		Description: "Targeting on bidders only",
@@ -324,6 +329,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				},
 			},
 		},
+		TruncateTargetAttr: nil,
 	},
 	{
 		Description: "Full basic targeting with hd_format",
@@ -364,6 +370,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				},
 			},
 		},
+		TruncateTargetAttr: nil,
 	},
 	{
 		Description: "Cache and deal targeting test",
@@ -410,6 +417,109 @@ var TargetingTests []TargetingTestData = []TargetingTestData{
 				},
 			},
 		},
+		TruncateTargetAttr: nil,
+	},
+	{
+		Description: "Truncate Targeting Attribute value is given and is less than const MaxKeyLength",
+		TargetData: targetData{
+			priceGranularity:  openrtb_ext.PriceGranularityFromString("med"),
+			includeBidderKeys: true,
+		},
+		Auction: auction{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				"ImpId-1": {
+					openrtb_ext.BidderAppnexus: {
+						bid:     bid123,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+					openrtb_ext.BidderRubicon: {
+						bid:     bid084,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+		},
+		ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{
+			"ImpId-1": {
+				openrtb_ext.BidderAppnexus: {
+					"hb_bidder_": "appnexus",
+					"hb_pb_appn": "1.20",
+				},
+				openrtb_ext.BidderRubicon: {
+					"hb_bidder_": "rubicon",
+					"hb_pb_rubi": "0.80",
+				},
+			},
+		},
+		TruncateTargetAttr: &truncateTargetAttrValueLess,
+	},
+	{
+		Description: "Truncate Targeting Attribute value is given and is greater than const MaxKeyLength",
+		TargetData: targetData{
+			priceGranularity:  openrtb_ext.PriceGranularityFromString("med"),
+			includeBidderKeys: true,
+		},
+		Auction: auction{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				"ImpId-1": {
+					openrtb_ext.BidderAppnexus: {
+						bid:     bid123,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+					openrtb_ext.BidderRubicon: {
+						bid:     bid084,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+		},
+		ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{
+			"ImpId-1": {
+				openrtb_ext.BidderAppnexus: {
+					"hb_bidder_appnexus": "appnexus",
+					"hb_pb_appnexus":     "1.20",
+				},
+				openrtb_ext.BidderRubicon: {
+					"hb_bidder_rubicon": "rubicon",
+					"hb_pb_rubicon":     "0.80",
+				},
+			},
+		},
+		TruncateTargetAttr: &truncateTargetAttrValueGreater,
+	},
+	{
+		Description: "Truncate Targeting Attribute value is given and is negative",
+		TargetData: targetData{
+			priceGranularity:  openrtb_ext.PriceGranularityFromString("med"),
+			includeBidderKeys: true,
+		},
+		Auction: auction{
+			winningBidsByBidder: map[string]map[openrtb_ext.BidderName]*pbsOrtbBid{
+				"ImpId-1": {
+					openrtb_ext.BidderAppnexus: {
+						bid:     bid123,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+					openrtb_ext.BidderRubicon: {
+						bid:     bid084,
+						bidType: openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+		},
+		ExpectedBidTargetsByBidder: map[string]map[openrtb_ext.BidderName]map[string]string{
+			"ImpId-1": {
+				openrtb_ext.BidderAppnexus: {
+					"hb_bidder_appnexus": "appnexus",
+					"hb_pb_appnexus":     "1.20",
+				},
+				openrtb_ext.BidderRubicon: {
+					"hb_bidder_rubicon": "rubicon",
+					"hb_pb_rubicon":     "0.80",
+				},
+			},
+		},
+		TruncateTargetAttr: &truncateTargetAttrValueNegative,
 	},
 }
 
@@ -433,7 +543,7 @@ func TestSetTargeting(t *testing.T) {
 		}
 		auc.winningBids = winningBids
 		targData := test.TargetData
-		targData.setTargeting(auc, test.IsApp, test.CategoryMapping)
+		targData.setTargeting(auc, test.IsApp, test.CategoryMapping, test.TruncateTargetAttr)
 		for imp, targetsByBidder := range test.ExpectedBidTargetsByBidder {
 			for bidder, expected := range targetsByBidder {
 				assert.Equal(t,
diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go
index 1198d0bb..13ac1f5c 100644
--- a/privacy/scrubber_test.go
+++ b/privacy/scrubber_test.go
@@ -37,7 +37,7 @@ func TestScrubDevice(t *testing.T) {
 		geo         ScrubStrategyGeo
 	}{
 		{
-			description: "All Strageties - None",
+			description: "All Strategies - None",
 			expected:    device,
 			id:          ScrubStrategyDeviceIDNone,
 			ipv4:        ScrubStrategyIPV4None,
@@ -45,7 +45,7 @@ func TestScrubDevice(t *testing.T) {
 			geo:         ScrubStrategyGeoNone,
 		},
 		{
-			description: "All Strageties - Strictest",
+			description: "All Strategies - Strictest",
 			expected: &openrtb2.Device{
 				DIDMD5:   "",
 				DIDSHA1:  "",
diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json
index 76bafff7..07492f75 100644
--- a/stored_requests/data/by_id/accounts/test.json
+++ b/stored_requests/data/by_id/accounts/test.json
@@ -10,5 +10,10 @@
     },
     "events": {
         "enabled": true
+    },
+    "cookie_sync": {
+        "default_limt": 20,
+        "max_limit": 50,
+        "default_coop_sync": true
     }
 }
diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go
index 27551c9a..c02eec15 100644
--- a/util/task/ticker_task_test.go
+++ b/util/task/ticker_task_test.go
@@ -1,6 +1,7 @@
 package task_test
 
 import (
+	"sync"
 	"testing"
 	"time"
 
@@ -9,55 +10,78 @@ import (
 )
 
 type MockRunner struct {
-	RunCount int
+	ExpectationMet chan struct{}
+	actualCalls    int
+	expectedCalls  int
+	mutex          sync.Mutex
 }
 
-func (mcc *MockRunner) Run() error {
-	mcc.RunCount++
-	return nil
+func NewMockRunner(expectedCalls int) *MockRunner {
+	return &MockRunner{
+		ExpectationMet: make(chan struct{}),
+		expectedCalls:  expectedCalls,
+	}
 }
 
-func TestStartWithSingleRun(t *testing.T) {
-	// Setup:
-	runner := &MockRunner{RunCount: 0}
-	interval := 0 * time.Millisecond
-	ticker := task.NewTickerTask(interval, runner)
+func (m *MockRunner) Run() error {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	m.actualCalls++
 
-	// Execute:
-	ticker.Start()
-	time.Sleep(10 * time.Millisecond)
+	if m.expectedCalls == m.actualCalls {
+		close(m.ExpectationMet)
+	}
 
-	// Verify:
-	assert.Equal(t, runner.RunCount, 1, "runner should have run one time")
+	return nil
 }
 
-func TestStartWithPeriodicRun(t *testing.T) {
-	// Setup:
-	runner := &MockRunner{RunCount: 0}
-	interval := 10 * time.Millisecond
+func (m *MockRunner) RunCount() int {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	return m.actualCalls
+}
+
+func TestStartWithSingleRun(t *testing.T) {
+	// Setup Initial Run Only:
+	expectedRuns := 1
+	runner := NewMockRunner(expectedRuns)
+	interval := 0 * time.Millisecond // forces a single run
 	ticker := task.NewTickerTask(interval, runner)
 
 	// Execute:
 	ticker.Start()
-	time.Sleep(25 * time.Millisecond)
-	ticker.Stop()
 
 	// Verify:
-	assert.Equal(t, runner.RunCount, 3, "runner should have run three times")
+	select {
+	case <-runner.ExpectationMet:
+	case <-time.After(250 * time.Millisecond):
+		assert.Failf(t, "Runner Calls", "expected %v calls, observed %v calls", expectedRuns, runner.RunCount())
+	}
+
+	// Verify No Additional Runs:
+	time.Sleep(50 * time.Millisecond)
+	assert.Equal(t, expectedRuns, runner.RunCount(), "runner should not run after Stop is called")
 }
 
-func TestStop(t *testing.T) {
-	// Setup:
-	runner := &MockRunner{RunCount: 0}
+func TestStartWithPeriodicRun(t *testing.T) {
+	// Setup Initial Run + One Periodic Run:
+	expectedRuns := 2
+	runner := NewMockRunner(expectedRuns)
 	interval := 10 * time.Millisecond
 	ticker := task.NewTickerTask(interval, runner)
 
 	// Execute:
 	ticker.Start()
-	time.Sleep(25 * time.Millisecond)
-	ticker.Stop()
-	time.Sleep(25 * time.Millisecond) // wait in case stop failed so additional runs can happen
 
-	// Verify:
-	assert.Equal(t, runner.RunCount, 3, "runner should have run three times")
+	// Verify Expected Runs:
+	select {
+	case <-runner.ExpectationMet:
+		ticker.Stop()
+	case <-time.After(250 * time.Millisecond):
+		assert.Failf(t, "Runner Calls", "expected %v calls, observed %v calls", expectedRuns, runner.RunCount())
+	}
+
+	// Verify No Additional Runs After Stop:
+	time.Sleep(50 * time.Millisecond)
+	assert.Equal(t, expectedRuns, runner.RunCount(), "runner should not run after Stop is called")
 }