Repository: prebid/prebid-server
Pull request #2348 (2022-08-09 22:26:08)

FPD bidder specific user 
Fix for fpd bidder specific user and no user in request, 
https://github.com/prebid/prebid-server/issues/2234


Patch:
diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go
index 72b91059..a0207f4a 100644
--- a/adapters/33across/33across.go
+++ b/adapters/33across/33across.go
@@ -48,7 +48,7 @@ type bidExt struct {
 }
 
 type bidTtxExt struct {
-	MediaType string `json:mediaType,omitempty`
+	MediaType string `json:"mediaType,omitempty"`
 }
 
 // MakeRequests create the object for TTX Reqeust.
diff --git a/endpoints/setuid.go b/endpoints/setuid.go
index 716044a2..712178a0 100644
--- a/endpoints/setuid.go
+++ b/endpoints/setuid.go
@@ -61,6 +61,7 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use
 			w.WriteHeader(http.StatusBadRequest)
 			w.Write([]byte(err.Error()))
 			metricsEngine.RecordSetUid(metrics.SetUidSyncerUnknown)
+			so.Errors = []error{err}
 			so.Status = http.StatusBadRequest
 			return
 		}
@@ -71,6 +72,7 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use
 			w.WriteHeader(http.StatusBadRequest)
 			w.Write([]byte(err.Error()))
 			metricsEngine.RecordSetUid(metrics.SetUidBadRequest)
+			so.Errors = []error{err}
 			so.Status = http.StatusBadRequest
 			return
 		}
@@ -82,8 +84,17 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use
 		account, fetchErrs := accountService.GetAccount(context.Background(), cfg, accountsFetcher, accountID)
 		if len(fetchErrs) > 0 {
 			w.WriteHeader(http.StatusBadRequest)
-			w.Write([]byte(combineErrors(fetchErrs).Error()))
-			metricsEngine.RecordSetUid(metrics.SetUidBadRequest)
+			err := combineErrors(fetchErrs)
+			w.Write([]byte(err.Error()))
+			switch err {
+			case errCookieSyncAccountBlocked:
+				metricsEngine.RecordSetUid(metrics.SetUidAccountBlocked)
+			case errCookieSyncAccountInvalid:
+				metricsEngine.RecordSetUid(metrics.SetUidAccountInvalid)
+			default:
+				metricsEngine.RecordSetUid(metrics.SetUidBadRequest)
+			}
+			so.Errors = []error{err}
 			so.Status = http.StatusBadRequest
 			return
 		}
@@ -99,6 +110,7 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use
 			case http.StatusUnavailableForLegalReasons:
 				metricsEngine.RecordSetUid(metrics.SetUidGDPRHostCookieBlocked)
 			}
+			so.Errors = []error{errors.New(body)}
 			so.Status = status
 			return
 		}
diff --git a/exchange/utils.go b/exchange/utils.go
index 8a4e0b68..96e5e2a9 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -3,14 +3,14 @@ package exchange
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"math/rand"
 
-	"github.com/prebid/prebid-server/stored_responses"
-
 	"github.com/buger/jsonparser"
 	"github.com/mxmCherry/openrtb/v16/openrtb2"
 	"github.com/prebid/go-gdpr/vendorconsent"
+
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/firstpartydata"
@@ -21,6 +21,7 @@ import (
 	"github.com/prebid/prebid-server/privacy/ccpa"
 	"github.com/prebid/prebid-server/privacy/lmt"
 	"github.com/prebid/prebid-server/schain"
+	"github.com/prebid/prebid-server/stored_responses"
 )
 
 var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{
@@ -55,7 +56,7 @@ func cleanOpenRTBRequests(ctx context.Context,
 		return
 	}
 
-	allowedBidderRequests = make([]BidderRequest, 0, 0)
+	allowedBidderRequests = make([]BidderRequest, 0)
 
 	bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses)
 
@@ -217,7 +218,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,
 		return nil, []error{err}
 	}
 
-	bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParamsEmbeddedMap(req.BidRequest)
+	bidderParamsInReqExt, err := adapters.ExtractReqExtBidderParamsMap(req.BidRequest)
 	if err != nil {
 		return nil, []error{err}
 	}
@@ -236,23 +237,10 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,
 
 		sChainWriter.Write(&reqCopy, bidder)
 
-		if len(bidderParamsInReqExt) != 0 {
-
-			// Update bidder-params(requestExt.Prebid.BidderParams) for the bidder to only contain bidder-params for
-			// this bidder only and remove bidder-params for all other bidders from requestExt.Prebid.BidderParams
-			params, err := getBidderParamsForBidder(bidderParamsInReqExt, bidder)
-			if err != nil {
-				return nil, []error{err}
-			}
-
-			requestExt.Prebid.BidderParams = params
-		}
-
-		reqExt, err := getExtJson(req.BidRequest, requestExt)
+		reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt)
 		if err != nil {
 			return nil, []error{err}
 		}
-		reqCopy.Ext = reqExt
 
 		if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil {
 			errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err))
@@ -285,26 +273,41 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,
 	return bidderRequests, errs
 }
 
-func getBidderParamsForBidder(bidderParamsInReqExt map[string]map[string]json.RawMessage, bidder string) (json.RawMessage, error) {
-	var params json.RawMessage
-	if bidderParams, ok := bidderParamsInReqExt[bidder]; ok {
-		var err error
-		params, err = json.Marshal(bidderParams)
-		if err != nil {
-			return nil, err
-		}
+func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage) (json.RawMessage, error) {
+	if len(requestExt) == 0 || requestExtParsed == nil {
+		return json.RawMessage(``), nil
 	}
-	return params, nil
-}
 
-func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) {
-	if len(req.Ext) == 0 || unpackedExt == nil {
-		return json.RawMessage(``), nil
+	// Resolve Bidder Params
+	var bidderParams json.RawMessage
+	if bidderParamsInReqExt != nil {
+		bidderParams = bidderParamsInReqExt[bidder]
+	}
+
+	// Copy Allowed Fields
+	// Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary
+	prebid := openrtb_ext.ExtRequestPrebid{
+		Integration:         requestExtParsed.Prebid.Integration,
+		Channel:             requestExtParsed.Prebid.Channel,
+		Debug:               requestExtParsed.Prebid.Debug,
+		CurrencyConversions: requestExtParsed.Prebid.CurrencyConversions,
+		BidderParams:        bidderParams,
+	}
+
+	// Marshal New Prebid Object
+	prebidJson, err := json.Marshal(prebid)
+	if err != nil {
+		return nil, err
 	}
 
-	extCopy := *unpackedExt
-	extCopy.Prebid.SChains = nil
-	return json.Marshal(extCopy)
+	// Update Ext With Prebid Json
+	extMap := make(map[string]json.RawMessage)
+	if err := json.Unmarshal(requestExt, &extMap); err != nil {
+		return nil, err
+	}
+	extMap["prebid"] = prebidJson
+
+	return json.Marshal(extMap)
 }
 
 // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext.
@@ -396,17 +399,28 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) {
 }
 
 func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) {
-	sanitizedImpExt := make(map[string]json.RawMessage, 3)
+	sanitizedImpExt := make(map[string]json.RawMessage, 6)
+	sanitizedImpPrebidExt := make(map[string]json.RawMessage, 2)
+
+	// copy allowed imp[].ext.prebid fields
+	if v, exists := impExtPrebid["is_rewarded_inventory"]; exists {
+		sanitizedImpPrebidExt["is_rewarded_inventory"] = v
+	}
+
+	if v, exists := impExtPrebid["options"]; exists {
+		sanitizedImpPrebidExt["options"] = v
+	}
 
-	delete(impExtPrebid, openrtb_ext.PrebidExtBidderKey)
-	if len(impExtPrebid) > 0 {
-		if impExtPrebidJSON, err := json.Marshal(impExtPrebid); err == nil {
+	// marshal sanitized imp[].ext.prebid
+	if len(sanitizedImpPrebidExt) > 0 {
+		if impExtPrebidJSON, err := json.Marshal(sanitizedImpPrebidExt); err == nil {
 			sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON
 		} else {
 			return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err)
 		}
 	}
 
+	// copy reserved imp[].ext fields known to not be bidder names
 	if v, exists := impExt[openrtb_ext.FirstPartyDataExtKey]; exists {
 		sanitizedImpExt[openrtb_ext.FirstPartyDataExtKey] = v
 	}
@@ -647,7 +661,7 @@ func extractBidRequestExt(bidRequest *openrtb2.BidRequest) (*openrtb_ext.ExtRequ
 	requestExt := &openrtb_ext.ExtRequest{}
 
 	if bidRequest == nil {
-		return requestExt, fmt.Errorf("Error bidRequest should not be nil")
+		return requestExt, errors.New("Error bidRequest should not be nil")
 	}
 
 	if len(bidRequest.Ext) > 0 {
diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go
index 64997d70..318fff40 100644
--- a/firstpartydata/first_party_data.go
+++ b/firstpartydata/first_party_data.go
@@ -187,13 +187,11 @@ func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, gl
 		return nil, nil
 	}
 
-	if bidRequestUser == nil && fpdConfigUser != nil {
-		return nil, &errortypes.BadInput{
-			Message: fmt.Sprintf("incorrect First Party Data for bidder %s: User object is not defined in request, but defined in FPD config", bidderName),
-		}
+	newUser := openrtb2.User{}
+	if bidRequestUser != nil {
+		newUser = *bidRequestUser
 	}
 
-	newUser := *bidRequestUser
 	var err error
 
 	//apply global fpd
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 5c6493cb..31bfb15f 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -315,6 +315,8 @@ func CookieSyncStatuses() []CookieSyncStatus {
 		CookieSyncBadRequest,
 		CookieSyncOptOut,
 		CookieSyncGDPRHostCookieBlocked,
+		CookieSyncAccountBlocked,
+		CookieSyncAccountInvalid,
 	}
 }
 
@@ -347,6 +349,8 @@ const (
 	SetUidBadRequest            SetUidStatus = "bad_request"
 	SetUidOptOut                SetUidStatus = "opt_out"
 	SetUidGDPRHostCookieBlocked SetUidStatus = "gdpr_blocked_host_cookie"
+	SetUidAccountBlocked        SetUidStatus = "acct_blocked"
+	SetUidAccountInvalid        SetUidStatus = "acct_invalid"
 	SetUidSyncerUnknown         SetUidStatus = "syncer_unknown"
 )
 
@@ -357,6 +361,8 @@ func SetUidStatuses() []SetUidStatus {
 		SetUidBadRequest,
 		SetUidOptOut,
 		SetUidGDPRHostCookieBlocked,
+		SetUidAccountBlocked,
+		SetUidAccountInvalid,
 		SetUidSyncerUnknown,
 	}
 }
diff --git a/openrtb_ext/imp_deepintent.go b/openrtb_ext/imp_deepintent.go
index 34c537d4..b50c8fe0 100644
--- a/openrtb_ext/imp_deepintent.go
+++ b/openrtb_ext/imp_deepintent.go
@@ -1,5 +1,5 @@
 package openrtb_ext
 
 type ExtImpDeepintent struct {
-	TagID string `json:tagId`
+	TagID string `json:"tagId"`
 }
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index 0fcbf4a7..b3807c5d 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -38,27 +38,26 @@ type ExtRequestPrebid struct {
 	Aliases              map[string]string         `json:"aliases,omitempty"`
 	AliasGVLIDs          map[string]uint16         `json:"aliasgvlids,omitempty"`
 	BidAdjustmentFactors map[string]float64        `json:"bidadjustmentfactors,omitempty"`
+	BidderConfigs        []BidderConfig            `json:"bidderconfig,omitempty"`
+	BidderParams         json.RawMessage           `json:"bidderparams,omitempty"`
 	Cache                *ExtRequestPrebidCache    `json:"cache,omitempty"`
 	Channel              *ExtRequestPrebidChannel  `json:"channel,omitempty"`
+	CurrencyConversions  *ExtRequestCurrency       `json:"currency,omitempty"`
 	Data                 *ExtRequestPrebidData     `json:"data,omitempty"`
 	Debug                bool                      `json:"debug,omitempty"`
-	Integration          string                    `json:"integration,omitempty"`
 	Events               json.RawMessage           `json:"events,omitempty"`
+	Experiment           *Experiment               `json:"experiment,omitempty"`
+	Integration          string                    `json:"integration,omitempty"`
+	Passthrough          json.RawMessage           `json:"passthrough,omitempty"`
 	SChains              []*ExtRequestPrebidSChain `json:"schains,omitempty"`
 	StoredRequest        *ExtStoredRequest         `json:"storedrequest,omitempty"`
 	SupportDeals         bool                      `json:"supportdeals,omitempty"`
 	Targeting            *ExtRequestTargeting      `json:"targeting,omitempty"`
-	BidderParams         json.RawMessage           `json:"bidderparams,omitempty"`
 
 	// NoSale specifies bidders with whom the publisher has a legal relationship where the
 	// passing of personally identifiable information doesn't constitute a sale per CCPA law.
 	// The array may contain a single sstar ('*') entry to represent all bidders.
 	NoSale []string `json:"nosale,omitempty"`
-
-	CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"`
-	BidderConfigs       []BidderConfig      `json:"bidderconfig,omitempty"`
-	Passthrough         json.RawMessage     `json:"passthrough,omitempty"`
-	Experiment          *Experiment         `json:"experiment,omitempty"`
 }
 
 // Experiment defines if experimental features are available for the request
diff --git a/schain/schainwriter.go b/schain/schainwriter.go
index 37d7b631..dbbd91ba 100644
--- a/schain/schainwriter.go
+++ b/schain/schainwriter.go
@@ -79,11 +79,5 @@ func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) {
 
 // extPrebidSChainExists checks if an schain exists in the ORTB 2.5 req.ext.prebid.schain location
 func extPrebidSChainExists(reqExt *openrtb_ext.ExtRequest) bool {
-	if reqExt == nil {
-		return false
-	}
-	if reqExt.Prebid.SChains == nil {
-		return false
-	}
-	return true
+	return reqExt != nil && reqExt.Prebid.SChains != nil
 }
diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml
index 8c46525c..1efa8af0 100644
--- a/static/bidder-info/ix.yaml
+++ b/static/bidder-info/ix.yaml
@@ -18,4 +18,6 @@ userSync:
   redirect:
     url: "https://ssum.casalemedia.com/usermatchredir?s=194962&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
     userMacro: ""
+  iframe:
+    url: "https://ssum-sec.casalemedia.com/usermatch?s=184674&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
     # ix appends the user id to end of the redirect url and does not utilize a macro


Test patch:
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index e9b32322..295eeaf1 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
@@ -255,10 +256,12 @@ func TestSetUIDEndpoint(t *testing.T) {
 		},
 	}
 
+	analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{})
 	metrics := &metricsConf.NilMetricsEngine{}
+
 	for _, test := range testCases {
-		response := doRequest(makeRequest(test.uri, test.existingSyncs), metrics,
-			test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed)
+		response := doRequest(makeRequest(test.uri, test.existingSyncs), analytics, metrics,
+			test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false)
 		assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description)
 
 		if test.expectedSyncs != nil {
@@ -295,8 +298,10 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 		cookies                []*usersync.Cookie
 		syncersBidderNameToKey map[string]string
 		gdprAllowsHostCookies  bool
+		cfgAccountRequired     bool
 		expectedResponseCode   int
 		expectedMetrics        func(*metrics.MetricsEngineMock)
+		expectedAnalytics      func(*MockAnalytics)
 	}{
 		{
 			description:            "Success - Sync",
@@ -309,6 +314,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 				m.On("RecordSetUid", metrics.SetUidOK).Once()
 				m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  200,
+					Bidder:  "pubmatic",
+					UID:     "123",
+					Errors:  []error{},
+					Success: true,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 		{
 			description:            "Success - Unsync",
@@ -321,6 +336,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 				m.On("RecordSetUid", metrics.SetUidOK).Once()
 				m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  200,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{},
+					Success: true,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 		{
 			description:            "Cookie Opted Out",
@@ -332,6 +357,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 			expectedMetrics: func(m *metrics.MetricsEngineMock) {
 				m.On("RecordSetUid", metrics.SetUidOptOut).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  401,
+					Bidder:  "",
+					UID:     "",
+					Errors:  []error{},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 		{
 			description:            "Unknown Syncer Key",
@@ -343,6 +378,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 			expectedMetrics: func(m *metrics.MetricsEngineMock) {
 				m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  400,
+					Bidder:  "",
+					UID:     "",
+					Errors:  []error{errors.New("The bidder name provided is not supported by Prebid Server")},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 		{
 			description:            "Unknown Format",
@@ -354,6 +399,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 			expectedMetrics: func(m *metrics.MetricsEngineMock) {
 				m.On("RecordSetUid", metrics.SetUidBadRequest).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  400,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{errors.New(`"f" query param is invalid. must be "b" or "i"`)},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 		{
 			description:            "Prevented By GDPR - Invalid Consent String",
@@ -365,6 +420,16 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 			expectedMetrics: func(m *metrics.MetricsEngineMock) {
 				m.On("RecordSetUid", metrics.SetUidBadRequest).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  400,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{errors.New("gdpr_consent is required when gdpr=1")},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 		{
 			description:            "Prevented By GDPR - Permission Denied By Consent String",
@@ -376,10 +441,88 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 			expectedMetrics: func(m *metrics.MetricsEngineMock) {
 				m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once()
 			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  451,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{errors.New("The gdpr_consent string prevents cookies from being saved")},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
+		},
+		{
+			description:            "Blocked account",
+			uri:                    "/setuid?bidder=pubmatic&uid=123&account=blocked_acct",
+			cookies:                []*usersync.Cookie{},
+			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
+			gdprAllowsHostCookies:  true,
+			expectedResponseCode:   400,
+			expectedMetrics: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordSetUid", metrics.SetUidAccountBlocked).Once()
+			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  400,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{errCookieSyncAccountBlocked},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
+		},
+		{
+			description:            "Invalid account",
+			uri:                    "/setuid?bidder=pubmatic&uid=123&account=unknown",
+			cookies:                []*usersync.Cookie{},
+			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
+			gdprAllowsHostCookies:  true,
+			cfgAccountRequired:     true,
+			expectedResponseCode:   400,
+			expectedMetrics: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordSetUid", metrics.SetUidAccountInvalid).Once()
+			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  400,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{errCookieSyncAccountInvalid},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
+		},
+		{
+			description:            "Malformed account",
+			uri:                    "/setuid?bidder=pubmatic&uid=123&account=malformed_acct",
+			cookies:                []*usersync.Cookie{},
+			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
+			gdprAllowsHostCookies:  true,
+			cfgAccountRequired:     true,
+			expectedResponseCode:   400,
+			expectedMetrics: func(m *metrics.MetricsEngineMock) {
+				m.On("RecordSetUid", metrics.SetUidBadRequest).Once()
+			},
+			expectedAnalytics: func(a *MockAnalytics) {
+				expected := analytics.SetUIDObject{
+					Status:  400,
+					Bidder:  "pubmatic",
+					UID:     "",
+					Errors:  []error{errors.New("json: cannot unmarshal string into Go struct field Account.disabled of type bool")},
+					Success: false,
+				}
+				a.On("LogSetUIDObject", &expected).Once()
+			},
 		},
 	}
 
 	for _, test := range testCases {
+		analyticsEngine := &MockAnalytics{}
+		test.expectedAnalytics(analyticsEngine)
+
 		metricsEngine := &metrics.MetricsEngineMock{}
 		test.expectedMetrics(metricsEngine)
 
@@ -387,9 +530,10 @@ func TestSetUIDEndpointMetrics(t *testing.T) {
 		for _, v := range test.cookies {
 			addCookie(req, v)
 		}
-		response := doRequest(req, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false)
+		response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired)
 
 		assert.Equal(t, test.expectedResponseCode, response.Code, test.description)
+		analyticsEngine.AssertExpectations(t)
 		metricsEngine.AssertExpectations(t)
 	}
 }
@@ -400,8 +544,9 @@ func TestOptedOut(t *testing.T) {
 	cookie.SetOptOut(true)
 	addCookie(request, cookie)
 	syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"}
+	analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{})
 	metrics := &metricsConf.NilMetricsEngine{}
-	response := doRequest(request, metrics, syncersBidderNameToKey, true, false, false)
+	response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false)
 
 	assert.Equal(t, http.StatusUnauthorized, response.Code)
 }
@@ -537,8 +682,13 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request {
 	return request
 }
 
-func doRequest(req *http.Request, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError bool) *httptest.ResponseRecorder {
-	cfg := config.Configuration{}
+func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool) *httptest.ResponseRecorder {
+	cfg := config.Configuration{
+		AccountRequired: cfgAccountRequired,
+		BlacklistedAcctMap: map[string]bool{
+			"blocked_acct": true,
+		},
+	}
 	cfg.MarshalAccountDefaults()
 
 	query := req.URL.Query()
@@ -557,15 +707,15 @@ func doRequest(req *http.Request, metrics metrics.MetricsEngine, syncersBidderNa
 		cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
 	}.Builder
 
-	analytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics)
 	syncersByBidder := make(map[string]usersync.Syncer)
 	for bidderName, syncerKey := range syncersBidderNameToKey {
 		syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame}
 	}
 
 	fakeAccountsFetcher := FakeAccountsFetcher{AccountData: map[string]json.RawMessage{
-		"valid_acct":    json.RawMessage(`{"disabled":false}`),
-		"disabled_acct": json.RawMessage(`{"disabled":true}`),
+		"valid_acct":     json.RawMessage(`{"disabled":false}`),
+		"disabled_acct":  json.RawMessage(`{"disabled":true}`),
+		"malformed_acct": json.RawMessage(`{"disabled":"malformed"}`),
 	}}
 
 	endpoint := NewSetUIDEndpoint(&cfg, syncersByBidder, gdprPermsBuilder, tcf2ConfigBuilder, analytics, fakeAccountsFetcher, metrics)
diff --git a/exchange/exchangetest/aliases.json b/exchange/exchangetest/aliases.json
index 59f270e1..8c0bf309 100644
--- a/exchange/exchangetest/aliases.json
+++ b/exchange/exchangetest/aliases.json
@@ -58,11 +58,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              }
-            }
+            "prebid": {}
           }
         }
       },
@@ -94,11 +90,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              }
-            }
+            "prebid": {}
           }
         }
       },
diff --git a/exchange/exchangetest/bidadjustmentfactors.json b/exchange/exchangetest/bidadjustmentfactors.json
index 1f096bfb..8ef413ee 100644
--- a/exchange/exchangetest/bidadjustmentfactors.json
+++ b/exchange/exchangetest/bidadjustmentfactors.json
@@ -56,15 +56,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              },
-              "bidadjustmentfactors": {
-                "appnexus": 2.5,
-                "districtm": 0.3
-              }
-            }
+            "prebid": {}
           }
         },
         "bidAdjustments": {
@@ -97,15 +89,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              },
-              "bidadjustmentfactors": {
-                "appnexus": 2.5,
-                "districtm": 0.3
-              }
-            }
+            "prebid": {}
           }
         },
         "bidAdjustments": {
@@ -129,4 +113,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/ccpa-nosale-any-bidder.json b/exchange/exchangetest/ccpa-nosale-any-bidder.json
index db98878d..1774ba7a 100644
--- a/exchange/exchangetest/ccpa-nosale-any-bidder.json
+++ b/exchange/exchangetest/ccpa-nosale-any-bidder.json
@@ -6,17 +6,19 @@
             "site": {
                 "page": "test.somepage.com"
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "appnexus": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }],
+            ],
             "regs": {
                 "ext": {
                     "us_privacy": "1-Y-"
@@ -40,26 +42,26 @@
                     "site": {
                         "page": "test.somepage.com"
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }],
+                    ],
                     "regs": {
                         "ext": {
                             "us_privacy": "1-Y-"
                         }
                     },
                     "ext": {
-                        "prebid": {
-                            "nosale": ["*"]
-                        }
+                        "prebid": {}
                     },
                     "user": {
                         "buyeruid": "some-buyer-id"
diff --git a/exchange/exchangetest/ccpa-nosale-specific-bidder.json b/exchange/exchangetest/ccpa-nosale-specific-bidder.json
index ffa4e9c7..219aac48 100644
--- a/exchange/exchangetest/ccpa-nosale-specific-bidder.json
+++ b/exchange/exchangetest/ccpa-nosale-specific-bidder.json
@@ -6,17 +6,19 @@
             "site": {
                 "page": "test.somepage.com"
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "appnexus": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }],
+            ],
             "regs": {
                 "ext": {
                     "us_privacy": "1-Y-"
@@ -40,26 +42,26 @@
                     "site": {
                         "page": "test.somepage.com"
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }],
+                    ],
                     "regs": {
                         "ext": {
                             "us_privacy": "1-Y-"
                         }
                     },
                     "ext": {
-                        "prebid": {
-                            "nosale": ["appnexus"]
-                        }
+                        "prebid": {}
                     },
                     "user": {
                         "buyeruid": "some-buyer-id"
diff --git a/exchange/exchangetest/eidpermissions-allowed-alias.json b/exchange/exchangetest/eidpermissions-allowed-alias.json
index 305c1823..fc12c271 100644
--- a/exchange/exchangetest/eidpermissions-allowed-alias.json
+++ b/exchange/exchangetest/eidpermissions-allowed-alias.json
@@ -7,10 +7,12 @@
             },
             "user": {
                 "ext": {
-                    "eids": [{
-                        "source": "source1",
-                        "id": "anyId"
-                    }]
+                    "eids": [
+                        {
+                            "source": "source1",
+                            "id": "anyId"
+                        }
+                    ]
                 }
             },
             "ext": {
@@ -19,24 +21,28 @@
                         "foo": "appnexus"
                     },
                     "data": {
-                        "eidpermissions": [{
-                            "source": "source1",
-                            "bidders": ["foo"]
-                        }]
+                        "eidpermissions": [
+                            {
+                                "source": "source1",
+                                "bidders": ["foo"]
+                            }
+                        ]
                     }
                 }
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "foo": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "foo": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }]
+            ]
         }
     },
     "outgoingRequests": {
@@ -49,76 +55,78 @@
                     },
                     "user": {
                         "ext": {
-                            "eids": [{
-                                "source": "source1",
-                                "id": "anyId"
-                            }]
+                            "eids": [
+                                {
+                                    "source": "source1",
+                                    "id": "anyId"
+                                }
+                            ]
                         }
                     },
                     "ext": {
-                        "prebid": {
-                            "aliases": {
-                                "foo": "appnexus"
-                            },
-                            "data": {
-                                "eidpermissions": [{
-                                    "source": "source1",
-                                    "bidders": ["foo"]
-                                }]
-                            }
-                        }
+                        "prebid": {}
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }]
+                    ]
                 }
             },
             "mockResponse": {
-                "pbsSeatBids": [{
-                    "pbsBids": [{
-                        "ortbBid": {
-                            "id": "apn-bid",
-                            "impid": "my-imp-id",
-                            "price": 0.3,
-                            "w": 200,
-                            "h": 250,
-                            "crid": "creative-1"
-                        },
-                        "bidType": "video"
-                    }],
-                    "seat": "foo"
-                }]
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "my-imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 250,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "video"
+                            }
+                        ],
+                        "seat": "foo"
+                    }
+                ]
             }
         }
     },
     "response": {
         "bids": {
             "id": "some-request-id",
-            "seatbid": [{
-                "seat": "foo",
-                "bid": [{
-                    "id": "apn-bid",
-                    "impid": "my-imp-id",
-                    "price": 0.3,
-                    "w": 200,
-                    "h": 250,
-                    "crid": "creative-1",
-                    "ext": {
-                        "origbidcpm": 0.3,
-                        "prebid": {
-                            "type": "video"
+            "seatbid": [
+                {
+                    "seat": "foo",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "my-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "video"
+                                }
+                            }
                         }
-                    }
-                }]
-            }]
+                    ]
+                }
+            ]
         }
     }
 }
\ No newline at end of file
diff --git a/exchange/exchangetest/eidpermissions-allowed.json b/exchange/exchangetest/eidpermissions-allowed.json
index 4687ec5c..953f38f9 100644
--- a/exchange/exchangetest/eidpermissions-allowed.json
+++ b/exchange/exchangetest/eidpermissions-allowed.json
@@ -7,33 +7,39 @@
             },
             "user": {
                 "ext": {
-                    "eids": [{
-                        "source": "source1",
-                        "id": "anyId"
-                    }]
+                    "eids": [
+                        {
+                            "source": "source1",
+                            "id": "anyId"
+                        }
+                    ]
                 }
             },
             "ext": {
                 "prebid": {
                     "data": {
-                        "eidpermissions": [{
-                            "source": "source1",
-                            "bidders": ["appnexus"]
-                        }]
+                        "eidpermissions": [
+                            {
+                                "source": "source1",
+                                "bidders": ["appnexus"]
+                            }
+                        ]
                     }
                 }
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "appnexus": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }]
+            ]
         }
     },
     "outgoingRequests": {
@@ -46,73 +52,78 @@
                     },
                     "user": {
                         "ext": {
-                            "eids": [{
-                                "source": "source1",
-                                "id": "anyId"
-                            }]
+                            "eids": [
+                                {
+                                    "source": "source1",
+                                    "id": "anyId"
+                                }
+                            ]
                         }
                     },
                     "ext": {
-                        "prebid": {
-                            "data": {
-                                "eidpermissions": [{
-                                    "source": "source1",
-                                    "bidders": ["appnexus"]
-                                }]
-                            }
-                        }
+                        "prebid": {}
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }]
+                    ]
                 }
             },
             "mockResponse": {
-                "pbsSeatBids": [{
-                    "pbsBids": [{
-                        "ortbBid": {
-                            "id": "apn-bid",
-                            "impid": "my-imp-id",
-                            "price": 0.3,
-                            "w": 200,
-                            "h": 250,
-                            "crid": "creative-1"
-                        },
-                        "bidType": "video"
-                    }],
-                    "seat": "appnexus"
-                }]
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "my-imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 250,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "video"
+                            }
+                        ],
+                        "seat": "appnexus"
+                    }
+                ]
             }
         }
     },
     "response": {
         "bids": {
             "id": "some-request-id",
-            "seatbid": [{
-                "seat": "appnexus",
-                "bid": [{
-                    "id": "apn-bid",
-                    "impid": "my-imp-id",
-                    "price": 0.3,
-                    "w": 200,
-                    "h": 250,
-                    "crid": "creative-1",
-                    "ext": {
-                        "origbidcpm": 0.3,
-                        "prebid": {
-                            "type": "video"
+            "seatbid": [
+                {
+                    "seat": "appnexus",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "my-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "video"
+                                }
+                            }
                         }
-                    }
-                }]
-            }]
+                    ]
+                }
+            ]
         }
     }
 }
\ No newline at end of file
diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json
index 373616f9..1fb91294 100644
--- a/exchange/exchangetest/eidpermissions-denied.json
+++ b/exchange/exchangetest/eidpermissions-denied.json
@@ -7,33 +7,39 @@
             },
             "user": {
                 "ext": {
-                    "eids": [{
-                        "source": "source1",
-                        "id": "anyId"
-                    }]
+                    "eids": [
+                        {
+                            "source": "source1",
+                            "id": "anyId"
+                        }
+                    ]
                 }
             },
             "ext": {
                 "prebid": {
                     "data": {
-                        "eidpermissions": [{
-                            "source": "source1",
-                            "bidders": ["otherBidder"]
-                        }]
+                        "eidpermissions": [
+                            {
+                                "source": "source1",
+                                "bidders": ["otherBidder"]
+                            }
+                        ]
                     }
                 }
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "appnexus": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }]
+            ]
         }
     },
     "outgoingRequests": {
@@ -44,69 +50,71 @@
                     "site": {
                         "page": "test.somepage.com"
                     },
-                    "user": {
-                    },
+                    "user": {},
                     "ext": {
-                        "prebid": {
-                            "data": {
-                                "eidpermissions": [{
-                                    "source": "source1",
-                                    "bidders": ["otherBidder"]
-                                }]
-                            }
-                        }
+                        "prebid": {}
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }]
+                    ]
                 }
             },
             "mockResponse": {
-                "pbsSeatBids": [{
-                    "pbsBids": [{
-                        "ortbBid": {
-                            "id": "apn-bid",
-                            "impid": "my-imp-id",
-                            "price": 0.3,
-                            "w": 200,
-                            "h": 250,
-                            "crid": "creative-1"
-                        },
-                        "bidType": "video"
-                    }],
-                    "seat": "appnexus"
-                }]
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "my-imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 250,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "video"
+                            }
+                        ],
+                        "seat": "appnexus"
+                    }
+                ]
             }
         }
     },
     "response": {
         "bids": {
             "id": "some-request-id",
-            "seatbid": [{
-                "seat": "appnexus",
-                "bid": [{
-                    "id": "apn-bid",
-                    "impid": "my-imp-id",
-                    "price": 0.3,
-                    "w": 200,
-                    "h": 250,
-                    "crid": "creative-1",
-                    "ext": {
-                        "origbidcpm": 0.3,
-                        "prebid": {
-                            "type": "video"
+            "seatbid": [
+                {
+                    "seat": "appnexus",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "my-imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "video"
+                                }
+                            }
                         }
-                    }
-                }]
-            }]
+                    ]
+                }
+            ]
         }
     }
 }
\ No newline at end of file
diff --git a/exchange/exchangetest/firstpartydata-multibidder-config-valid.json b/exchange/exchangetest/firstpartydata-multibidder-config-valid.json
index 8ac7e7d2..8788d553 100644
--- a/exchange/exchangetest/firstpartydata-multibidder-config-valid.json
+++ b/exchange/exchangetest/firstpartydata-multibidder-config-valid.json
@@ -105,11 +105,7 @@
             "page": "fpd_appnexus_site_page"
           },
           "ext": {
-            "prebid": {
-              "data": {
-                "eidpermissions": null
-              }
-            }
+            "prebid": {}
           }
         }
       }
@@ -147,15 +143,10 @@
             "page": "fpd_rubicon_site_page"
           },
           "ext": {
-            "prebid": {
-              "data": {
-                "eidpermissions": null
-              }
-            }
+            "prebid": {}
           }
         }
       }
     }
   }
-
 }
\ No newline at end of file
diff --git a/exchange/exchangetest/passthrough_imp_only.json b/exchange/exchangetest/passthrough_imp_only.json
index 84e1a72b..e389a213 100644
--- a/exchange/exchangetest/passthrough_imp_only.json
+++ b/exchange/exchangetest/passthrough_imp_only.json
@@ -51,11 +51,6 @@
                         "ext": {
                             "bidder": {
                                 "placementId": 1
-                            },
-                            "prebid": {
-                                "passthrough": {
-                                    "imp_passthrough_val": 20
-                                }
                             }
                         }
                     },
diff --git a/exchange/exchangetest/passthrough_root_and_imp.json b/exchange/exchangetest/passthrough_root_and_imp.json
index d71bc5f2..423bad37 100644
--- a/exchange/exchangetest/passthrough_root_and_imp.json
+++ b/exchange/exchangetest/passthrough_root_and_imp.json
@@ -45,18 +45,11 @@
                         "ext": {
                             "bidder": {
                                 "placementId": 1
-                            },
-                            "prebid": {
-                                "passthrough": {
-                                    "imp_passthrough_val": 20
-                                }
                             }
                         }
                     }],
                     "ext": {
-                        "prebid": {
-                            "passthrough": {"bid_response_passthrough":20}
-                        }
+                        "prebid": {}
                     }
                 }
             },
diff --git a/exchange/exchangetest/passthrough_root_only.json b/exchange/exchangetest/passthrough_root_only.json
index 6b759e7a..c84f1927 100644
--- a/exchange/exchangetest/passthrough_root_only.json
+++ b/exchange/exchangetest/passthrough_root_only.json
@@ -44,9 +44,7 @@
                         }
                     }],
                     "ext": {
-                        "prebid": {
-                            "passthrough": {"bid_response_passthrough":20}
-                        }
+                        "prebid": {}
                     }
                 }
             },
diff --git a/exchange/exchangetest/request-ext-prebid-filtering.json b/exchange/exchangetest/request-ext-prebid-filtering.json
new file mode 100644
index 00000000..1d699c8a
--- /dev/null
+++ b/exchange/exchangetest/request-ext-prebid-filtering.json
@@ -0,0 +1,160 @@
+{
+    "description": "Verifies only allowed bid.request.ext.prebid values are passed to the bidder, leaving other ext fields untouched.",
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "test-request-id",
+            "site": {
+                "page": "test.testpage.com"
+            },
+            "imp": [
+                {
+                    "id": "imp-id",
+                    "video": {
+                        "mimes": [
+                            "video/mp4"
+                        ]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
+                    }
+                }
+            ],
+            "ext": {
+                "other": "test-other",
+                "prebid": {
+                    "data": {
+                        "eidpermissions": [
+                            {
+                                "source": "test-source",
+                                "bidders": [
+                                    "appnexus"
+                                ]
+                            }
+                        ]
+                    },
+                    "integration": "test-integration",
+                    "channel": {
+                        "name": "test-name",
+                        "version": "test-version"
+                    },
+                    "debug": true,
+                    "currency": {
+                        "rates": {
+                            "FOO": {
+                                "BAR": 42
+                            }
+                        },
+                        "usepbsrates": true
+                    },
+                    "bidderparams": {
+                        "appnexus": {
+                            "param1": 1,
+                            "paramA": "A"
+                        },
+                        "rubicon": {
+                            "not": "permitted-for-appnexus"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "test-request-id",
+                    "site": {
+                        "page": "test.testpage.com"
+                    },
+                    "imp": [
+                        {
+                            "id": "imp-id",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
+                            }
+                        }
+                    ],
+                    "ext": {
+                        "other": "test-other",
+                        "prebid": {
+                            "integration": "test-integration",
+                            "channel": {
+                                "name": "test-name",
+                                "version": "test-version"
+                            },
+                            "debug": true,
+                            "currency": {
+                                "rates": {
+                                    "FOO": {
+                                        "BAR": 42
+                                    }
+                                },
+                                "usepbsrates": true
+                            },
+                            "bidderparams": {
+                                "param1": 1,
+                                "paramA": "A"
+                            }
+                        }
+                    }
+                }
+            },
+            "mockResponse": {
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 250,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "video"
+                            }
+                        ],
+                        "seat": "appnexus"
+                    }
+                ]
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "test-request-id",
+            "seatbid": [
+                {
+                    "seat": "appnexus",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "video"
+                                }
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/request-imp-ext-prebid-filtering.json b/exchange/exchangetest/request-imp-ext-prebid-filtering.json
new file mode 100644
index 00000000..fb8e0892
--- /dev/null
+++ b/exchange/exchangetest/request-imp-ext-prebid-filtering.json
@@ -0,0 +1,136 @@
+{
+    "description": "Verifies only allowed bid.request.imp[].ext.prebid values are passed to the bidder, dropping other fields.",
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "test-request-id",
+            "site": {
+                "page": "test.testpage.com"
+            },
+            "imp": [
+                {
+                    "id": "imp-id",
+                    "video": {
+                        "mimes": [
+                            "video/mp4"
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                }
+                            },
+                            "storedrequest": {
+                                "id": "any-id"
+                            },
+                            "storedauctionresponse": {
+                                "id": "any-id"
+                            },
+                            "storedbidresponse": [
+                                {
+                                    "id": "any-id",
+                                    "bidder": "foo"
+                                }
+                            ],
+                            "is_rewarded_inventory": true,
+                            "options": {
+                                "echovideoattrs": true
+                            },
+                            "unknown": "any-unknown"
+                        },
+                        "data": "any-data",
+                        "context": "any-context",
+                        "skadn": "any-skadn",
+                        "gpid": "any-gpid",
+                        "tid": "any-tid"
+                    }
+                }
+            ]
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "test-request-id",
+                    "site": {
+                        "page": "test.testpage.com"
+                    },
+                    "imp": [
+                        {
+                            "id": "imp-id",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                },
+                                "prebid": {
+                                    "is_rewarded_inventory": true,
+                                    "options": {
+                                        "echovideoattrs": true
+                                    }
+                                },
+                                "data": "any-data",
+                                "context": "any-context",
+                                "skadn": "any-skadn",
+                                "gpid": "any-gpid",
+                                "tid": "any-tid"
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "pbsSeatBids": [
+                    {
+                        "pbsBids": [
+                            {
+                                "ortbBid": {
+                                    "id": "apn-bid",
+                                    "impid": "imp-id",
+                                    "price": 0.3,
+                                    "w": 200,
+                                    "h": 250,
+                                    "crid": "creative-1"
+                                },
+                                "bidType": "video"
+                            }
+                        ],
+                        "seat": "appnexus"
+                    }
+                ]
+            }
+        }
+    },
+    "response": {
+        "bids": {
+            "id": "test-request-id",
+            "seatbid": [
+                {
+                    "seat": "appnexus",
+                    "bid": [
+                        {
+                            "id": "apn-bid",
+                            "impid": "imp-id",
+                            "price": 0.3,
+                            "w": 200,
+                            "h": 250,
+                            "crid": "creative-1",
+                            "ext": {
+                                "origbidcpm": 0.3,
+                                "prebid": {
+                                    "type": "video"
+                                }
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/schain-host-and-request.json b/exchange/exchangetest/schain-host-and-request.json
index 7a071124..88f407cd 100644
--- a/exchange/exchangetest/schain-host-and-request.json
+++ b/exchange/exchangetest/schain-host-and-request.json
@@ -6,33 +6,39 @@
             "site": {
                 "page": "test.somepage.com"
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "appnexus": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }],
+            ],
             "ext": {
                 "prebid": {
                     "bidadjustmentfactors": null,
-                    "schains": [{
-                        "bidders": ["appnexus"],
-                        "schain": {
-                            "complete": 1,
-                            "nodes": [{
-                                "asi": "whatever.com",
-                                "sid": "1234",
-                                "rid": "123-456-7890",
-                                "hp": 1
-                            }],
-                            "ver": "2.0"
+                    "schains": [
+                        {
+                            "bidders": ["appnexus"],
+                            "schain": {
+                                "complete": 1,
+                                "nodes": [
+                                    {
+                                        "asi": "whatever.com",
+                                        "sid": "1234",
+                                        "rid": "123-456-7890",
+                                        "hp": 1
+                                    }
+                                ],
+                                "ver": "2.0"
+                            }
                         }
-                    }],
+                    ],
                     "debug": true,
                     "nosale": ["appnexus"]
                 }
@@ -47,21 +53,22 @@
                     "site": {
                         "page": "test.somepage.com"
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }],
+                    ],
                     "ext": {
                         "prebid": {
-                            "debug": true,
-                            "nosale": ["appnexus"]
+                            "debug": true
                         }
                     },
                     "source": {
@@ -69,21 +76,21 @@
                             "schain": {
                                 "complete": 1,
                                 "nodes": [
-                                  {
-                                    "asi": "whatever.com",
-                                    "sid": "1234",
-                                    "rid": "123-456-7890",
-                                    "hp": 1
-                                  },
-                                  {
-                                    "asi": "pbshostcompany.com",
-                                    "sid": "00001",
-                                    "rid": "BidRequest",
-                                    "hp": 1
-                                  }
+                                    {
+                                        "asi": "whatever.com",
+                                        "sid": "1234",
+                                        "rid": "123-456-7890",
+                                        "hp": 1
+                                    },
+                                    {
+                                        "asi": "pbshostcompany.com",
+                                        "sid": "00001",
+                                        "rid": "BidRequest",
+                                        "hp": 1
+                                    }
                                 ],
                                 "ver": "2.0"
-                              }
+                            }
                         }
                     }
                 },
diff --git a/exchange/exchangetest/schain-host-only.json b/exchange/exchangetest/schain-host-only.json
index 1750a8b4..d58947e3 100644
--- a/exchange/exchangetest/schain-host-only.json
+++ b/exchange/exchangetest/schain-host-only.json
@@ -6,17 +6,19 @@
             "site": {
                 "page": "test.somepage.com"
             },
-            "imp": [{
-                "id": "my-imp-id",
-                "video": {
-                    "mimes": ["video/mp4"]
-                },
-                "ext": {
-                    "appnexus": {
-                        "placementId": 1
+            "imp": [
+                {
+                    "id": "my-imp-id",
+                    "video": {
+                        "mimes": ["video/mp4"]
+                    },
+                    "ext": {
+                        "appnexus": {
+                            "placementId": 1
+                        }
                     }
                 }
-            }],
+            ],
             "ext": {
                 "prebid": {
                     "debug": true,
@@ -33,21 +35,22 @@
                     "site": {
                         "page": "test.somepage.com"
                     },
-                    "imp": [{
-                        "id": "my-imp-id",
-                        "video": {
-                            "mimes": ["video/mp4"]
-                        },
-                        "ext": {
-                            "bidder": {
-                                "placementId": 1
+                    "imp": [
+                        {
+                            "id": "my-imp-id",
+                            "video": {
+                                "mimes": ["video/mp4"]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
                             }
                         }
-                    }],
+                    ],
                     "ext": {
                         "prebid": {
-                            "debug": true,
-                            "nosale": ["appnexus"]
+                            "debug": true
                         }
                     },
                     "source": {
@@ -55,15 +58,15 @@
                             "schain": {
                                 "complete": 0,
                                 "nodes": [
-                                  {
-                                    "asi": "pbshostcompany.com",
-                                    "sid": "00001",
-                                    "rid": "BidRequest",
-                                    "hp": 1
-                                  }
+                                    {
+                                        "asi": "pbshostcompany.com",
+                                        "sid": "00001",
+                                        "rid": "BidRequest",
+                                        "hp": 1
+                                    }
                                 ],
                                 "ver": "1.0"
-                              }
+                            }
                         }
                     }
                 },
diff --git a/exchange/exchangetest/tricky-userids.json b/exchange/exchangetest/tricky-userids.json
index e01f53c7..065ec812 100644
--- a/exchange/exchangetest/tricky-userids.json
+++ b/exchange/exchangetest/tricky-userids.json
@@ -73,11 +73,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              }
-            }
+            "prebid": {}
           }
         }
       },
@@ -111,11 +107,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              }
-            }
+            "prebid": {}
           }
         }
       },
@@ -147,11 +139,7 @@
             }
           ],
           "ext": {
-            "prebid": {
-              "aliases": {
-                "districtm": "appnexus"
-              }
-            }
+            "prebid": {}
           }
         }
       },
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 600d0721..4ff2aa0a 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -265,13 +265,14 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 			expectedError:     "",
 		},
 		{
-			description: "imp.ext.prebid - Bidders Only",
+			description: "imp.ext.prebid - Bidder Only",
 			givenImpExt: map[string]json.RawMessage{
 				"prebid":  json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`),
 				"data":    json.RawMessage(`"anyData"`),
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			givenImpExtPrebid: map[string]json.RawMessage{
 				"bidder": json.RawMessage(`"anyBidder"`),
@@ -281,28 +282,55 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			expectedError: "",
 		},
 		{
-			description: "imp.ext.prebid - Bidders + Other Values",
+			description: "imp.ext.prebid - Bidder + Other Forbidden Value",
 			givenImpExt: map[string]json.RawMessage{
 				"prebid":  json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`),
 				"data":    json.RawMessage(`"anyData"`),
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			givenImpExtPrebid: map[string]json.RawMessage{
 				"bidder":    json.RawMessage(`"anyBidder"`),
-				"someOther": json.RawMessage(`"value"`),
+				"forbidden": json.RawMessage(`"anyValue"`),
 			},
 			expected: map[string]json.RawMessage{
-				"prebid":  json.RawMessage(`{"someOther":"value"}`),
 				"data":    json.RawMessage(`"anyData"`),
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
+			},
+			expectedError: "",
+		},
+		{
+			description: "imp.ext.prebid - Bidder + Other Allowed Values",
+			givenImpExt: map[string]json.RawMessage{
+				"prebid":  json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`),
+				"data":    json.RawMessage(`"anyData"`),
+				"context": json.RawMessage(`"anyContext"`),
+				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
+				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
+			},
+			givenImpExtPrebid: map[string]json.RawMessage{
+				"bidder":                json.RawMessage(`"anyBidder"`),
+				"is_rewarded_inventory": json.RawMessage(`"anyIsRewardedInventory"`),
+				"options":               json.RawMessage(`"anyOptions"`),
+			},
+			expected: map[string]json.RawMessage{
+				"prebid":  json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`),
+				"data":    json.RawMessage(`"anyData"`),
+				"context": json.RawMessage(`"anyContext"`),
+				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
+				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			expectedError: "",
 		},
@@ -314,6 +342,7 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 				"context":   json.RawMessage(`"anyContext"`),
 				"skadn":     json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":      json.RawMessage(`"anyGPID"`),
+				"tid":       json.RawMessage(`"anyTID"`),
 			},
 			givenImpExtPrebid: map[string]json.RawMessage{},
 			expected: map[string]json.RawMessage{
@@ -321,11 +350,12 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			expectedError: "",
 		},
 		{
-			description: "imp.ext + imp.ext.prebid - Prebid Bidders Only",
+			description: "imp.ext + imp.ext.prebid - Prebid Bidder Only",
 			givenImpExt: map[string]json.RawMessage{
 				"anyBidder": json.RawMessage(`"anyBidderValues"`),
 				"prebid":    json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`),
@@ -333,6 +363,7 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 				"context":   json.RawMessage(`"anyContext"`),
 				"skadn":     json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":      json.RawMessage(`"anyGPID"`),
+				"tid":       json.RawMessage(`"anyTID"`),
 			},
 			givenImpExtPrebid: map[string]json.RawMessage{
 				"bidder": json.RawMessage(`"anyBidder"`),
@@ -342,11 +373,12 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			expectedError: "",
 		},
 		{
-			description: "imp.ext + imp.ext.prebid - Prebid Bidders + Other Values",
+			description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Forbidden Value",
 			givenImpExt: map[string]json.RawMessage{
 				"anyBidder": json.RawMessage(`"anyBidderValues"`),
 				"prebid":    json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`),
@@ -358,10 +390,35 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 			},
 			givenImpExtPrebid: map[string]json.RawMessage{
 				"bidder":    json.RawMessage(`"anyBidder"`),
-				"someOther": json.RawMessage(`"value"`),
+				"forbidden": json.RawMessage(`"anyValue"`),
+			},
+			expected: map[string]json.RawMessage{
+				"data":    json.RawMessage(`"anyData"`),
+				"context": json.RawMessage(`"anyContext"`),
+				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
+				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
+			},
+			expectedError: "",
+		},
+		{
+			description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Allowed Values",
+			givenImpExt: map[string]json.RawMessage{
+				"anyBidder": json.RawMessage(`"anyBidderValues"`),
+				"prebid":    json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`),
+				"data":      json.RawMessage(`"anyData"`),
+				"context":   json.RawMessage(`"anyContext"`),
+				"skadn":     json.RawMessage(`"anySKAdNetwork"`),
+				"gpid":      json.RawMessage(`"anyGPID"`),
+				"tid":       json.RawMessage(`"anyTID"`),
+			},
+			givenImpExtPrebid: map[string]json.RawMessage{
+				"bidder":                json.RawMessage(`"anyBidder"`),
+				"is_rewarded_inventory": json.RawMessage(`"anyIsRewardedInventory"`),
+				"options":               json.RawMessage(`"anyOptions"`),
 			},
 			expected: map[string]json.RawMessage{
-				"prebid":  json.RawMessage(`{"someOther":"value"}`),
+				"prebid":  json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`),
 				"data":    json.RawMessage(`"anyData"`),
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
@@ -378,12 +435,13 @@ func TestCreateSanitizedImpExt(t *testing.T) {
 				"context": json.RawMessage(`"anyContext"`),
 				"skadn":   json.RawMessage(`"anySKAdNetwork"`),
 				"gpid":    json.RawMessage(`"anyGPID"`),
+				"tid":     json.RawMessage(`"anyTID"`),
 			},
 			givenImpExtPrebid: map[string]json.RawMessage{
-				"malformed": json.RawMessage(`json`), // String value without quotes.
+				"options": json.RawMessage(`malformed`), // String value without quotes.
 			},
 			expected:      nil,
-			expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'j' looking for beginning of value",
+			expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'm' looking for beginning of value",
 		},
 	}
 
@@ -2382,6 +2440,99 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
 	}
 }
 
+func TestBuildRequestExtForBidder(t *testing.T) {
+	bidder := "foo"
+	bidderParams := json.RawMessage(`"bar"`)
+
+	testCases := []struct {
+		description  string
+		requestExt   json.RawMessage
+		bidderParams map[string]json.RawMessage
+		expectedJson json.RawMessage
+	}{
+		{
+			description:  "Nil",
+			bidderParams: nil,
+			requestExt:   nil,
+			expectedJson: json.RawMessage(``),
+		},
+		{
+			description:  "Empty",
+			bidderParams: nil,
+			requestExt:   json.RawMessage(`{}`),
+			expectedJson: json.RawMessage(`{"prebid":{}}`),
+		},
+		{
+			description:  "Prebid - Allowed Fields Only",
+			bidderParams: nil,
+			requestExt:   json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+		},
+		{
+			description:  "Prebid - Allowed Fields + Bidder Params",
+			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
+			requestExt:   json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
+		},
+		{
+			description:  "Other",
+			bidderParams: nil,
+			requestExt:   json.RawMessage(`{"other":"foo"}`),
+			expectedJson: json.RawMessage(`{"other":"foo","prebid":{}}`),
+		},
+		{
+			description:  "Prebid + Other + Bider Params",
+			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
+			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}}}`),
+			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
+		},
+	}
+
+	for _, test := range testCases {
+		requestExtParsed := &openrtb_ext.ExtRequest{}
+		if test.requestExt != nil {
+			err := json.Unmarshal(test.requestExt, requestExtParsed)
+			if !assert.NoError(t, err, test.description+":parse_ext") {
+				continue
+			}
+		}
+
+		actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams)
+		if len(test.expectedJson) > 0 {
+			assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json")
+		} else {
+			assert.Equal(t, test.expectedJson, actualJson, test.description+":json")
+		}
+		assert.NoError(t, actualErr, test.description+":err")
+	}
+}
+
+func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) {
+	var (
+		bidder           = "foo"
+		requestExt       = json.RawMessage(`{}`)
+		requestExtParsed *openrtb_ext.ExtRequest
+		bidderParams     map[string]json.RawMessage
+	)
+
+	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams)
+	assert.Equal(t, json.RawMessage(``), actualJson)
+	assert.NoError(t, actualErr)
+}
+
+func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) {
+	var (
+		bidder           = "foo"
+		requestExt       = json.RawMessage(`malformed`)
+		requestExtParsed = &openrtb_ext.ExtRequest{}
+		bidderParams     map[string]json.RawMessage
+	)
+
+	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams)
+	assert.Equal(t, json.RawMessage(nil), actualJson)
+	assert.EqualError(t, actualErr, "invalid character 'm' looking for beginning of value")
+}
+
 // newAdapterAliasBidRequest builds a BidRequest with aliases
 func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest {
 	dnt := int8(1)
diff --git a/firstpartydata/first_party_data_test.go b/firstpartydata/first_party_data_test.go
index fc4409f9..5e0d448e 100644
--- a/firstpartydata/first_party_data_test.go
+++ b/firstpartydata/first_party_data_test.go
@@ -852,17 +852,6 @@ func TestResolveUserNilValues(t *testing.T) {
 	assert.Nil(t, resultUser, "Result user should be nil")
 }
 
-func TestResolveUserBadInput(t *testing.T) {
-	fpdConfigUser := make(map[string]json.RawMessage, 0)
-	fpdConfigUser["id"] = []byte(`"fpdConfigUserId"`)
-	fpdConfig := &openrtb_ext.ORTB2{User: fpdConfigUser}
-
-	resultUser, err := resolveUser(fpdConfig, nil, nil, nil, "appnexus")
-	assert.Error(t, err, "Error should be returned")
-	assert.Equal(t, "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config", err.Error(), "Incorrect error message")
-	assert.Nil(t, resultUser, "Result user should be nil")
-}
-
 func TestMergeUsers(t *testing.T) {
 
 	originalUser := &openrtb2.User{
diff --git a/firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json b/firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json
new file mode 100644
index 00000000..7ba65d1b
--- /dev/null
+++ b/firstpartydata/tests/extractfpdforbidders/req-empty-user-fpd-not-empty-user.json
@@ -0,0 +1,80 @@
+{
+  "description": "req.user is not present but req.ext.prebid.bidderconfig contains user for bidder; expect req.user to present in the resolved bidders FPD",
+  "inputRequestData": {
+    "id": "bid_id",
+    "test": 1,
+    "ext": {
+      "prebid": {
+        "data": {
+          "bidders": ["appnexus"]
+        },
+        "bidderconfig": [
+          {
+            "bidders": [
+              "appnexus"
+            ],
+            "config": {
+              "ortb2": {
+                "user": {
+                  "id": "appnexusFpdUser",
+                  "yob": 2011,
+                  "gender": "F",
+                  "keywords": "fpd keywords",
+                  "data": [
+                    {
+                      "id": "FpdUserDataId1",
+                      "name": "FpdUserDataName1"
+                    },
+                    {
+                      "id": "FpdUserDataId2",
+                      "name": "FpdUserDataName2"
+                    }
+                  ],
+                  "ext": {
+                    "data": {
+                      "userdata": "appnexusFpdUserExtData"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        ]
+      }
+    }
+  },
+  "outputRequestData":  {
+    "id": "bid_id",
+    "test": 1
+  },
+  "biddersFPDResolved": {
+    "appnexus": {
+      "user": {
+        "yob": 2011,
+        "gender": "F",
+        "keywords": "fpd keywords",
+        "data": [
+          {
+            "id": "FpdUserDataId1",
+            "name": "FpdUserDataName1"
+          },
+          {
+            "id": "FpdUserDataId2",
+            "name": "FpdUserDataName2"
+          }
+        ],
+        "ext": {
+          "data": {
+            "ext": {
+              "data": {
+                "userdata": "appnexusFpdUserExtData"
+              }
+            },
+            "id": "appnexusFpdUser"
+          }
+        }
+      }
+    }
+  },
+  "validationErrors": []
+}
diff --git a/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json
index a23b4539..dd06e8b4 100644
--- a/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json
+++ b/firstpartydata/tests/extractfpdforbidders/two-bidders-with-incorrect-fpd.json
@@ -52,7 +52,6 @@
   "outputRequestData":{},
   "biddersFPDResolved": {},
   "validationErrors": [
-    {"Message": "incorrect First Party Data for bidder appnexus: User object is not defined in request, but defined in FPD config"},
     {"Message": "incorrect First Party Data for bidder appnexus: App object is not defined in request, but defined in FPD config"},
     {"Message": "incorrect First Party Data for bidder telaria: App object is not defined in request, but defined in FPD config"}
   ]