Repository: prebid/prebid-server
Pull request #1772 (2021-03-23 00:39:31)

Generate seatbid[].bid[].ext.prebid.bidid
Implements https://github.com/prebid/prebid-server/issues/1171

VeronikaSolovei9: Unit tests in progress

Patch:
diff --git a/adapters/bidmachine/bidmachine.go b/adapters/bidmachine/bidmachine.go
new file mode 100644
index 00000000..3c9a5b67
--- /dev/null
+++ b/adapters/bidmachine/bidmachine.go
@@ -0,0 +1,219 @@
+package bidmachine
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"path"
+	"strconv"
+	"text/template"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint template.Template
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json")
+	headers.Add("Accept", "application/json")
+	headers.Add("X-Openrtb-Version", "2.5")
+
+	impressions := request.Imp
+	result := make([]*adapters.RequestData, 0, len(impressions))
+	errs := make([]error, 0, len(impressions))
+
+	for _, impression := range impressions {
+		if impression.Banner != nil {
+			banner := impression.Banner
+			if banner.W == nil && banner.H == nil {
+				if banner.Format == nil {
+					errs = append(errs, &errortypes.BadInput{
+						Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format is missing. At least one is required",
+					})
+					continue
+				}
+				if len(banner.Format) == 0 {
+					errs = append(errs, &errortypes.BadInput{
+						Message: "Impression with id: " + impression.ID + " has following error: Banner width and height is not provided and banner format array is empty. At least one is required",
+					})
+					continue
+				}
+			}
+
+		}
+
+		var bidderExt adapters.ExtImpBidder
+		err := json.Unmarshal(impression.Ext, &bidderExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		var impressionExt openrtb_ext.ExtImpBidmachine
+		err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		url, err := a.buildEndpointURL(impressionExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory == 1 {
+			if impression.Banner != nil && !hasRewardedBattr(impression.Banner.BAttr) {
+				bannerCopy := *impression.Banner
+				bannerCopy.BAttr = copyBAttrWithRewardedInventory(bannerCopy.BAttr)
+				impression.Banner = &bannerCopy
+			}
+			if impression.Video != nil && !hasRewardedBattr(impression.Video.BAttr) {
+				videoCopy := *impression.Video
+				videoCopy.BAttr = copyBAttrWithRewardedInventory(videoCopy.BAttr)
+				impression.Video = &videoCopy
+			}
+		}
+		request.Imp = []openrtb2.Imp{impression}
+		body, err := json.Marshal(request)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		result = append(result, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     url,
+			Body:    body,
+			Headers: headers,
+		})
+	}
+
+	request.Imp = impressions
+
+	return result, errs
+}
+
+func hasRewardedBattr(attr []openrtb2.CreativeAttribute) bool {
+	for i := 0; i < len(attr); i++ {
+		if attr[i] == openrtb2.CreativeAttribute(16) {
+			return true
+		}
+	}
+	return false
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	var errs []error
+
+	switch responseData.StatusCode {
+	case http.StatusNoContent:
+		return nil, nil
+	case http.StatusServiceUnavailable:
+		fallthrough
+	case http.StatusBadRequest:
+		fallthrough
+	case http.StatusUnauthorized:
+		fallthrough
+	case http.StatusForbidden:
+		return nil, []error{&errortypes.BadInput{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
+		}}
+	case http.StatusOK:
+		break
+	default:
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "unexpected status code: " + strconv.Itoa(responseData.StatusCode) + " " + string(responseData.Body),
+		}}
+	}
+
+	var bidResponse openrtb2.BidResponse
+	err := json.Unmarshal(responseData.Body, &bidResponse)
+	if err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: err.Error(),
+		}}
+	}
+
+	response := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+
+	for _, seatBid := range bidResponse.SeatBid {
+		for _, bid := range seatBid.Bid {
+			thisBid := bid
+			bidType := GetMediaTypeForImp(bid.ImpID, request.Imp)
+			if bidType == UndefinedMediaType {
+				errs = append(errs, &errortypes.BadServerResponse{
+					Message: "ignoring bid id=" + bid.ID + ", request doesn't contain any valid impression with id=" + bid.ImpID,
+				})
+				continue
+			}
+			response.Bids = append(response.Bids, &adapters.TypedBid{
+				Bid:     &thisBid,
+				BidType: bidType,
+			})
+		}
+	}
+
+	return response, errs
+}
+
+// Builder builds a new instance of the Bidmachine adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: *template,
+	}
+
+	return bidder, nil
+}
+
+const UndefinedMediaType = openrtb_ext.BidType("")
+
+func (a *adapter) buildEndpointURL(params openrtb_ext.ExtImpBidmachine) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{Host: params.Host}
+	uriString, errMacros := macros.ResolveMacros(a.endpoint, endpointParams)
+	if errMacros != nil {
+		return "", &errortypes.BadInput{
+			Message: "Failed to resolve host macros",
+		}
+	}
+	uri, errUrl := url.Parse(uriString)
+	if errUrl != nil || uri.Scheme == "" || uri.Host == "" {
+		return "", &errortypes.BadInput{
+			Message: "Failed to create final URL with provided host",
+		}
+	}
+	uri.Path = path.Join(uri.Path, params.Path)
+	uri.Path = path.Join(uri.Path, params.SellerID)
+	return uri.String(), nil
+}
+
+func copyBAttrWithRewardedInventory(src []openrtb2.CreativeAttribute) []openrtb2.CreativeAttribute {
+	dst := make([]openrtb2.CreativeAttribute, len(src))
+	copy(dst, src)
+	dst = append(dst, openrtb2.CreativeAttribute(16))
+	return dst
+}
+
+func GetMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impID {
+			if imp.Banner == nil && imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			}
+			return mediaType
+		}
+	}
+	return UndefinedMediaType
+}
diff --git a/adapters/criteo/criteo.go b/adapters/criteo/criteo.go
new file mode 100644
index 00000000..f045c9f0
--- /dev/null
+++ b/adapters/criteo/criteo.go
@@ -0,0 +1,114 @@
+package criteo
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	uri             string
+	slotIDGenerator slotIDGenerator
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, extraRequestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	criteoRequest, errs := newCriteoRequest(a.slotIDGenerator, request)
+	if len(errs) > 0 {
+		return nil, errs
+	}
+
+	jsonRequest, err := json.Marshal(criteoRequest)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	rqData := adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.uri,
+		Body:    jsonRequest,
+		Headers: getCriteoRequestHeaders(&criteoRequest),
+	}
+
+	return []*adapters.RequestData{&rqData}, nil
+}
+
+func getCriteoRequestHeaders(criteoRequest *criteoRequest) http.Header {
+	headers := http.Header{}
+
+	// criteoRequest is known not to be nil
+	// If there was an error generating it from newCriteoRequest, the errors will be returned immediately
+	// and this method won't be called
+
+	if criteoRequest.User.CookieID != "" {
+		headers.Add("Cookie", "uid="+criteoRequest.User.CookieID)
+	}
+
+	if criteoRequest.User.IP != "" {
+		headers.Add("X-Forwarded-For", criteoRequest.User.IP)
+	}
+
+	if criteoRequest.User.IPv6 != "" {
+		headers.Add("X-Forwarded-For", criteoRequest.User.IPv6)
+	}
+
+	if criteoRequest.User.UA != "" {
+		headers.Add("User-Agent", criteoRequest.User.UA)
+	}
+
+	return headers
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if response.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}}
+	}
+
+	bidResponse, err := newCriteoResponseFromBytes(response.Body)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	bidderResponse := adapters.NewBidderResponse()
+	bidderResponse.Bids = make([]*adapters.TypedBid, len(bidResponse.Slots))
+
+	for i := 0; i < len(bidResponse.Slots); i++ {
+		bidderResponse.Bids[i] = &adapters.TypedBid{
+			Bid: &openrtb2.Bid{
+				ID:    bidResponse.Slots[i].ID,
+				ImpID: bidResponse.Slots[i].ImpID,
+				Price: bidResponse.Slots[i].CPM,
+				AdM:   bidResponse.Slots[i].Creative,
+				W:     bidResponse.Slots[i].Width,
+				H:     bidResponse.Slots[i].Height,
+				CrID:  bidResponse.Slots[i].CreativeID,
+			},
+			BidType: openrtb_ext.BidTypeBanner,
+		}
+	}
+
+	return bidderResponse, nil
+}
+
+// Builder builds a new instance of the Criteo adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	return builderWithGuidGenerator(bidderName, config, newRandomSlotIDGenerator())
+}
+
+func builderWithGuidGenerator(bidderName openrtb_ext.BidderName, config config.Adapter, slotIDGenerator slotIDGenerator) (adapters.Bidder, error) {
+	return &adapter{
+		uri:             config.Endpoint,
+		slotIDGenerator: slotIDGenerator,
+	}, nil
+}
diff --git a/adapters/criteo/generators.go b/adapters/criteo/generators.go
new file mode 100644
index 00000000..0ae0ceca
--- /dev/null
+++ b/adapters/criteo/generators.go
@@ -0,0 +1,36 @@
+package criteo
+
+import "github.com/gofrs/uuid"
+
+type slotIDGenerator interface {
+	NewSlotID() (string, error)
+}
+
+type randomSlotIDGenerator struct{}
+
+func newRandomSlotIDGenerator() randomSlotIDGenerator {
+	return randomSlotIDGenerator{}
+}
+
+func (g randomSlotIDGenerator) NewSlotID() (string, error) {
+	guid, err := uuid.NewV4()
+	if err != nil {
+		return "", err
+	}
+
+	return guid.String(), nil
+}
+
+type fakeSlotIDGenerator struct {
+	fakeSlotID string
+}
+
+func newFakeGuidGenerator(fakeSlotID string) fakeSlotIDGenerator {
+	return fakeSlotIDGenerator{
+		fakeSlotID,
+	}
+}
+
+func (f fakeSlotIDGenerator) NewSlotID() (string, error) {
+	return f.fakeSlotID, nil
+}
diff --git a/adapters/criteo/models.go b/adapters/criteo/models.go
new file mode 100644
index 00000000..64f6081d
--- /dev/null
+++ b/adapters/criteo/models.go
@@ -0,0 +1,299 @@
+package criteo
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type criteoRequest struct {
+	ID          string                   `json:"id,omitempty"`
+	Publisher   criteoPublisher          `json:"publisher,omitempty"`
+	User        criteoUser               `json:"user,omitempty"`
+	GdprConsent criteoGdprConsent        `json:"gdprconsent,omitempty"`
+	Slots       []criteoRequestSlot      `json:"slots,omitempty"`
+	Eids        []openrtb_ext.ExtUserEid `json:"eids,omitempty"`
+}
+
+func newCriteoRequest(slotIDGenerator slotIDGenerator, request *openrtb2.BidRequest) (criteoRequest, []error) {
+	var errs []error
+
+	// request cannot be nil by design
+
+	criteoRequest := criteoRequest{}
+
+	criteoRequest.ID = request.ID
+
+	// Extracting request slots
+	if len(request.Imp) > 0 {
+		criteoSlots, slotsErr := newCriteoRequestSlots(slotIDGenerator, request.Imp)
+		if len(slotsErr) > 0 {
+			return criteoRequest, slotsErr
+		}
+		criteoRequest.Slots = criteoSlots
+	}
+
+	var networkId *int64
+	for _, criteoSlot := range criteoRequest.Slots {
+		if networkId == nil && criteoSlot.NetworkID != nil && *criteoSlot.NetworkID > 0 {
+			networkId = criteoSlot.NetworkID
+		} else if networkId != nil && criteoSlot.NetworkID != nil && *criteoSlot.NetworkID != *networkId {
+			return criteoRequest, []error{&errortypes.BadInput{
+				Message: "Bid request has slots coming with several network IDs which is not allowed",
+			}}
+		}
+	}
+
+	criteoRequest.Publisher = newCriteoPublisher(networkId, request.App, request.Site)
+
+	var regsExt *openrtb_ext.ExtRegs
+	if request.Regs != nil && request.Regs.Ext != nil {
+		if err := json.Unmarshal(request.Regs.Ext, &regsExt); err != nil {
+			errs = append(errs, err)
+		}
+	}
+
+	criteoRequest.User = newCriteoUser(request.User, request.Device, regsExt)
+
+	if gdprConsent, err := newCriteoGdprConsent(request.User, regsExt); err != nil {
+		errs = append(errs, err)
+	} else {
+		criteoRequest.GdprConsent = gdprConsent
+	}
+
+	if request.User != nil && request.User.Ext != nil {
+		var extUser openrtb_ext.ExtUser
+		if err := json.Unmarshal(request.User.Ext, &extUser); err != nil {
+			errs = append(errs, err)
+		} else {
+			criteoRequest.Eids = extUser.Eids
+		}
+	}
+
+	return criteoRequest, errs
+}
+
+type criteoPublisher struct {
+	SiteID    string `json:"siteid,omitempty"`
+	BundleID  string `json:"bundleid,omitempty"`
+	URL       string `json:"url,omitempty"`
+	NetworkID *int64 `json:"networkid,omitempty"`
+}
+
+func newCriteoPublisher(networkId *int64, app *openrtb2.App, site *openrtb2.Site) criteoPublisher {
+	// Both app and site cannot be nil at the same time by design in PBS
+
+	criteoPublisher := criteoPublisher{}
+
+	if networkId != nil && *networkId > 0 {
+		criteoPublisher.NetworkID = networkId
+	}
+
+	if app != nil {
+		criteoPublisher.BundleID = app.Bundle
+	}
+
+	if site != nil {
+		criteoPublisher.SiteID = site.ID
+		criteoPublisher.URL = site.Page
+	}
+
+	return criteoPublisher
+}
+
+type criteoUser struct {
+	DeviceID     string `json:"deviceid,omitempty"`
+	DeviceOS     string `json:"deviceos,omitempty"`
+	DeviceIDType string `json:"deviceidtype,omitempty"`
+	CookieID     string `json:"cookieuid,omitempty"`
+	UID          string `json:"uid,omitempty"`
+	IP           string `json:"ip,omitempty"`
+	IPv6         string `json:"ipv6,omitempty"`
+	UA           string `json:"ua,omitempty"`
+	UspIab       string `json:"uspIab,omitempty"`
+}
+
+func newCriteoUser(user *openrtb2.User, device *openrtb2.Device, regsExt *openrtb_ext.ExtRegs) criteoUser {
+	criteoUser := criteoUser{}
+
+	if user == nil && device == nil {
+		return criteoUser
+	}
+
+	if user != nil {
+		criteoUser.CookieID = user.BuyerUID
+	}
+
+	if device != nil {
+		deviceType := getDeviceType(device.OS)
+		criteoUser.DeviceIDType = deviceType
+
+		criteoUser.DeviceOS = device.OS
+		criteoUser.DeviceID = device.IFA
+		criteoUser.IP = device.IP
+		criteoUser.IPv6 = device.IPv6
+		criteoUser.UA = device.UA
+	}
+
+	if regsExt != nil {
+		criteoUser.UspIab = regsExt.USPrivacy // CCPA
+	}
+
+	return criteoUser
+}
+
+type criteoGdprConsent struct {
+	GdprApplies *bool  `json:"gdprapplies,omitempty"`
+	ConsentData string `json:"consentdata,omitempty"`
+}
+
+func newCriteoGdprConsent(user *openrtb2.User, regsExt *openrtb_ext.ExtRegs) (criteoGdprConsent, error) {
+	consent := criteoGdprConsent{}
+
+	if user == nil && regsExt == nil {
+		return consent, nil
+	}
+
+	if user != nil && user.Ext != nil {
+		var userExt *openrtb_ext.ExtUser
+		if err := json.Unmarshal(user.Ext, &userExt); err != nil {
+			return consent, err
+		}
+		consent.ConsentData = userExt.Consent
+	}
+
+	if regsExt != nil {
+		if regsExt.GDPR != nil {
+			gdprApplies := bool((*regsExt.GDPR & 1) == 1)
+			consent.GdprApplies = &gdprApplies
+		}
+	}
+
+	return consent, nil
+}
+
+type criteoRequestSlot struct {
+	SlotID    string              `json:"slotid,omitempty"`
+	ImpID     string              `json:"impid,omitempty"`
+	ZoneID    *int64              `json:"zoneid,omitempty"`
+	NetworkID *int64              `json:"networkid,omitempty"`
+	Sizes     []criteoRequestSize `json:"sizes,omitempty"`
+}
+
+func newCriteoRequestSlots(slotIDGenerator slotIDGenerator, impressions []openrtb2.Imp) ([]criteoRequestSlot, []error) {
+	var errs []error
+
+	// `impressions` known not to be nil or empty by design, PBS checks it upstream.
+
+	// Criteo slot should comes with any of (both are ok as well):
+	//   - `zoneid`
+	//   - `networkid`, `slotid`, `sizes`
+	//
+	// if not, criteo will reject the slot.
+
+	var criteoSlots = make([]criteoRequestSlot, len(impressions))
+
+	for i := 0; i < len(impressions); i++ {
+		criteoSlots[i] = criteoRequestSlot{}
+
+		criteoSlots[i].ImpID = impressions[i].ID
+
+		// Generating a random slot ID
+		generatedSlotID, err := slotIDGenerator.NewSlotID()
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		criteoSlots[i].SlotID = generatedSlotID
+
+		if impressions[i].Banner != nil {
+			if impressions[i].Banner.Format != nil {
+				criteoSlots[i].Sizes = make([]criteoRequestSize, len(impressions[i].Banner.Format))
+				for idx, format := range impressions[i].Banner.Format {
+					criteoSlots[i].Sizes[idx] = newCriteoRequestSize(format.W, format.H)
+				}
+			} else if impressions[i].Banner.W != nil && *impressions[i].Banner.W > 0 && impressions[i].Banner.H != nil && *impressions[i].Banner.H > 0 {
+				criteoSlots[i].Sizes = make([]criteoRequestSize, 1)
+				criteoSlots[i].Sizes[0] = newCriteoRequestSize(*impressions[i].Banner.W, *impressions[i].Banner.H)
+			}
+		}
+
+		var bidderExt adapters.ExtImpBidder
+		if err := json.Unmarshal(impressions[i].Ext, &bidderExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		if bidderExt.Bidder != nil {
+			var criteoExt openrtb_ext.ExtImpCriteo
+			if err := json.Unmarshal(bidderExt.Bidder, &criteoExt); err != nil {
+				errs = append(errs, err)
+				continue
+			}
+			if criteoExt.ZoneID > 0 {
+				criteoSlots[i].ZoneID = &criteoExt.ZoneID
+			}
+			if criteoExt.NetworkID > 0 {
+				criteoSlots[i].NetworkID = &criteoExt.NetworkID
+			}
+		}
+	}
+
+	return criteoSlots, errs
+}
+
+type criteoRequestSize = string
+
+func newCriteoRequestSize(width int64, height int64) criteoRequestSize {
+	return fmt.Sprintf("%dx%d", width, height)
+}
+
+var deviceType = map[string]string{
+	"ios":     "idfa",
+	"android": "gaid",
+	"unknown": "unknown",
+}
+
+func getDeviceType(os string) string {
+	if os != "" {
+		if dtype, ok := deviceType[strings.ToLower(os)]; ok {
+			return dtype
+		}
+	}
+
+	return deviceType["unknown"]
+}
+
+type criteoResponse struct {
+	ID    string               `json:"id,omitempty"`
+	Slots []criteoResponseSlot `json:"slots,omitempty"`
+}
+
+func newCriteoResponseFromBytes(bytes []byte) (criteoResponse, error) {
+	var err error
+	var bidResponse criteoResponse
+
+	if err = json.Unmarshal(bytes, &bidResponse); err != nil {
+		return bidResponse, err
+	}
+
+	return bidResponse, nil
+}
+
+type criteoResponseSlot struct {
+	ID         string  `json:"id,omitempty"`
+	ImpID      string  `json:"impid,omitempty"`
+	ZoneID     int64   `json:"zoneid,omitempty"`
+	NetworkID  int64   `json:"networkid,omitempty"`
+	CPM        float64 `json:"cpm,omitempty"`
+	Currency   string  `json:"currency,omitempty"`
+	Width      int64   `json:"width,omitempty"`
+	Height     int64   `json:"height,omitempty"`
+	Creative   string  `json:"creative,omitempty"`
+	CreativeID string  `json:"creativeid,omitempty"`
+}
diff --git a/adapters/criteo/usersync.go b/adapters/criteo/usersync.go
new file mode 100644
index 00000000..3deba085
--- /dev/null
+++ b/adapters/criteo/usersync.go
@@ -0,0 +1,14 @@
+package criteo
+
+import (
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/usersync"
+)
+
+// NewCriteoSyncer user syncer for Criteo
+// Criteo doesn't need user synchronization yet.
+func NewCriteoSyncer(temp *template.Template) usersync.Usersyncer {
+	return adapters.NewSyncer("criteo", temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/dmx/dmx.go b/adapters/dmx/dmx.go
index 79e1d751..280c7187 100644
--- a/adapters/dmx/dmx.go
+++ b/adapters/dmx/dmx.go
@@ -48,6 +48,8 @@ type dmxParams struct {
 	Bidfloor    float64 `json:"bidfloor,omitempty"`
 }
 
+var protocols = []openrtb2.Protocol{2, 3, 5, 6, 7, 8}
+
 func UserSellerOrPubId(str1, str2 string) string {
 	if str1 != "" {
 		return str1
@@ -100,6 +102,12 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt
 		if dmxReq.App.ID != "" {
 			anyHasId = true
 		}
+		if anyHasId == false {
+			if idfa, valid := getIdfa(request); valid {
+				dmxReq.App.ID = idfa
+				anyHasId = true
+			}
+		}
 	} else {
 		dmxReq.App = nil
 	}
@@ -147,13 +155,7 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt
 		}
 	}
 
-	if anyHasId == false {
-		return nil, []error{errors.New("This request contained no identifier")}
-	}
-
 	for _, inst := range dmxReq.Imp {
-		var banner *openrtb2.Banner
-		var video *openrtb2.Video
 		var ins openrtb2.Imp
 		var params dmxExt
 		const intVal int8 = 1
@@ -164,9 +166,9 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt
 		if isDmxParams(params.Bidder) {
 			if inst.Banner != nil {
 				if len(inst.Banner.Format) != 0 {
-					banner = inst.Banner
+					bannerCopy := *inst.Banner
 					if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" {
-						imps = fetchParams(params, inst, ins, imps, banner, nil, intVal)
+						imps = fetchParams(params, inst, ins, imps, &bannerCopy, nil, intVal)
 					} else {
 						return nil, []error{errors.New("Missing Params for auction to be send")}
 					}
@@ -174,9 +176,9 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt
 			}
 
 			if inst.Video != nil {
-				video = inst.Video
+				videoCopy := *inst.Video
 				if params.Bidder.PublisherId != "" || params.Bidder.MemberId != "" {
-					imps = fetchParams(params, inst, ins, imps, nil, video, intVal)
+					imps = fetchParams(params, inst, ins, imps, nil, &videoCopy, intVal)
 				} else {
 					return nil, []error{errors.New("Missing Params for auction to be send")}
 				}
@@ -187,6 +189,10 @@ func (adapter *DmxAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapt
 
 	dmxReq.Imp = imps
 
+	if anyHasId == false {
+		return nil, []error{errors.New("This request contained no identifier")}
+	}
+
 	oJson, err := json.Marshal(dmxReq)
 
 	if err != nil {
@@ -271,10 +277,15 @@ func fetchParams(params dmxExt, inst openrtb2.Imp, ins openrtb2.Imp, imps []open
 		tempimp.Secure = &intVal
 	}
 	if banner != nil {
+		if banner.H == nil || banner.W == nil {
+			banner.H = &banner.Format[0].H
+			banner.W = &banner.Format[0].W
+		}
 		tempimp.Banner = banner
 	}
 
 	if video != nil {
+		video.Protocols = checkProtocols(video)
 		tempimp.Video = video
 	}
 
@@ -327,3 +338,22 @@ func isDmxParams(t interface{}) bool {
 		return false
 	}
 }
+
+func getIdfa(request *openrtb2.BidRequest) (string, bool) {
+	if request.Device == nil {
+		return "", false
+	}
+
+	device := request.Device
+
+	if device.IFA != "" {
+		return device.IFA, true
+	}
+	return "", false
+}
+func checkProtocols(imp *openrtb2.Video) []openrtb2.Protocol {
+	if len(imp.Protocols) > 0 {
+		return imp.Protocols
+	}
+	return protocols
+}
diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go
index abe2d90b..57abee63 100644
--- a/adapters/tappx/tappx.go
+++ b/adapters/tappx/tappx.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"net/url"
 	"strconv"
+	"strings"
 	"text/template"
 	"time"
 
@@ -17,7 +18,7 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
-const TAPPX_BIDDER_VERSION = "1.1"
+const TAPPX_BIDDER_VERSION = "1.2"
 const TYPE_CNN = "prebid"
 
 type TappxAdapter struct {
@@ -126,7 +127,9 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in
 		}
 	}
 
-	thisURI.Path += params.Endpoint
+	if !(strings.Contains(strings.ToLower(thisURI.Host), strings.ToLower(params.Endpoint))) {
+		thisURI.Path += params.Endpoint //Now version is backward compatible. In future, this condition and content will be delete
+	}
 
 	queryParams := url.Values{}
 
diff --git a/config/config.go b/config/config.go
index bc15a947..7b78fb26 100644
--- a/config/config.go
+++ b/config/config.go
@@ -80,6 +80,8 @@ type Configuration struct {
 	RequestValidation RequestValidation `mapstructure:"request_validation"`
 	// When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty
 	AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"`
+	//When true, new bid id will be generated in seatbid[].bid[].ext.prebid.bidid and used in event urls instead
+	GenerateBidID bool `mapstructure:"generate_bid_id"`
 }
 
 const MIN_COOKIE_SIZE_BYTES = 500
@@ -829,12 +831,14 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}")
 	v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um")
 	v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}")
+	v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io")
 	v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs")
 	v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb")
 	v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs")
 	v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2")
 	v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25")
 	v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx")
+	v.SetDefault("adapters.criteo.endpoint", "https://bidder.criteo.com/cdb?profileId=230")
 	v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
 	v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb")
 	v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid")
@@ -956,6 +960,7 @@ func SetupViper(v *viper.Viper, filename string) {
 	v.SetDefault("account_defaults.debug_allow", true)
 	v.SetDefault("certificates_file", "")
 	v.SetDefault("auto_gen_source_tid", true)
+	v.SetDefault("generate_bid_id", false)
 
 	v.SetDefault("request_timeout_headers.request_time_in_queue", "")
 	v.SetDefault("request_timeout_headers.request_timeout_in_queue", "")
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 46c96706..0ea28f8a 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -29,12 +29,14 @@ import (
 	"github.com/prebid/prebid-server/adapters/beachfront"
 	"github.com/prebid/prebid-server/adapters/beintoo"
 	"github.com/prebid/prebid-server/adapters/between"
+	"github.com/prebid/prebid-server/adapters/bidmachine"
 	"github.com/prebid/prebid-server/adapters/brightroll"
 	"github.com/prebid/prebid-server/adapters/colossus"
 	"github.com/prebid/prebid-server/adapters/connectad"
 	"github.com/prebid/prebid-server/adapters/consumable"
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
+	"github.com/prebid/prebid-server/adapters/criteo"
 	"github.com/prebid/prebid-server/adapters/datablocks"
 	"github.com/prebid/prebid-server/adapters/decenterads"
 	"github.com/prebid/prebid-server/adapters/deepintent"
@@ -137,12 +139,14 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderBeachfront:       beachfront.Builder,
 		openrtb_ext.BidderBeintoo:          beintoo.Builder,
 		openrtb_ext.BidderBetween:          between.Builder,
+		openrtb_ext.BidderBidmachine:       bidmachine.Builder,
 		openrtb_ext.BidderBrightroll:       brightroll.Builder,
 		openrtb_ext.BidderColossus:         colossus.Builder,
 		openrtb_ext.BidderConnectAd:        connectad.Builder,
 		openrtb_ext.BidderConsumable:       consumable.Builder,
 		openrtb_ext.BidderConversant:       conversant.Builder,
 		openrtb_ext.BidderCpmstar:          cpmstar.Builder,
+		openrtb_ext.BidderCriteo:           criteo.Builder,
 		openrtb_ext.BidderDatablocks:       datablocks.Builder,
 		openrtb_ext.BidderDecenterAds:      decenterads.Builder,
 		openrtb_ext.BidderDeepintent:       deepintent.Builder,
diff --git a/exchange/bidder.go b/exchange/bidder.go
index d6b8028c..23b42d2a 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -61,6 +61,7 @@ type adaptedBidder interface {
 // pbsOrtbBid.bidEvents is set by exchange when event tracking is enabled
 // pbsOrtbBid.dealPriority is optionally provided by adapters and used internally by the exchange to support deal targeted campaigns.
 // pbsOrtbBid.dealTierSatisfied is set to true by exchange.updateHbPbCatDur if deal tier satisfied otherwise it will be set to false
+// pbsOrtbBid.generatedBidID is unique bid id generated by prebid server if generate bid id option is enabled in config
 type pbsOrtbBid struct {
 	bid               *openrtb2.Bid
 	bidType           openrtb_ext.BidType
@@ -69,6 +70,7 @@ type pbsOrtbBid struct {
 	bidEvents         *openrtb_ext.ExtBidPrebidEvents
 	dealPriority      int
 	dealTierSatisfied bool
+	generatedBidID    string
 }
 
 // pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder.
@@ -325,25 +327,30 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset
 	return nativeRequests.Asset{}, fmt.Errorf("Unable to find asset with ID:%d in the request", id)
 }
 
+var authorizationHeader = http.CanonicalHeaderKey("authorization")
+
+func filterHeader(h http.Header) http.Header {
+	clone := h.Clone()
+	clone.Del(authorizationHeader)
+	return clone
+}
+
 // makeExt transforms information about the HTTP call into the contract class for the PBS response.
 func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
-	if httpInfo.err == nil {
-		return &openrtb_ext.ExtHttpCall{
-			Uri:            httpInfo.request.Uri,
-			RequestBody:    string(httpInfo.request.Body),
-			ResponseBody:   string(httpInfo.response.Body),
-			Status:         httpInfo.response.StatusCode,
-			RequestHeaders: httpInfo.request.Headers,
-		}
-	} else if httpInfo.request == nil {
-		return &openrtb_ext.ExtHttpCall{}
-	} else {
-		return &openrtb_ext.ExtHttpCall{
-			Uri:            httpInfo.request.Uri,
-			RequestBody:    string(httpInfo.request.Body),
-			RequestHeaders: httpInfo.request.Headers,
+	ext := &openrtb_ext.ExtHttpCall{}
+
+	if httpInfo != nil && httpInfo.request != nil {
+		ext.Uri = httpInfo.request.Uri
+		ext.RequestBody = string(httpInfo.request.Body)
+		ext.RequestHeaders = filterHeader(httpInfo.request.Headers)
+
+		if httpInfo.err == nil && httpInfo.response != nil {
+			ext.ResponseBody = string(httpInfo.response.Body)
+			ext.Status = httpInfo.response.StatusCode
 		}
 	}
+
+	return ext
 }
 
 // doRequest makes a request, handles the response, and returns the data needed by the
diff --git a/exchange/events.go b/exchange/events.go
index f15579c6..bedafddf 100644
--- a/exchange/events.go
+++ b/exchange/events.go
@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"time"
 
-	jsonpatch "github.com/evanphx/json-patch"
+	"github.com/evanphx/json-patch"
 	"github.com/prebid/prebid-server/analytics"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/endpoints/events"
@@ -62,7 +62,11 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ex
 		return
 	}
 	vastXML := makeVAST(bid)
-	if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bid.ID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok {
+	bidID := bid.ID
+	if len(pbsBid.generatedBidID) > 0 {
+		bidID = pbsBid.generatedBidID
+	}
+	if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok {
 		bid.AdM = newVastXML
 	}
 }
@@ -103,10 +107,14 @@ func (ev *eventTracking) makeBidExtEvents(pbsBid *pbsOrtbBid, bidderName openrtb
 
 // makeEventURL returns an analytics event url for the requested type (win or imp)
 func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) string {
+	bidId := pbsBid.bid.ID
+	if len(pbsBid.generatedBidID) > 0 {
+		bidId = pbsBid.generatedBidID
+	}
 	return events.EventRequestToUrl(ev.externalURL,
 		&analytics.EventRequest{
 			Type:      evType,
-			BidID:     pbsBid.bid.ID,
+			BidID:     bidId,
 			Bidder:    string(bidderName),
 			AccountID: ev.accountID,
 			Timestamp: ev.auctionTimestampMs,
diff --git a/exchange/exchange.go b/exchange/exchange.go
index e06f87bf..8dd6a06b 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -17,6 +17,7 @@ import (
 	"github.com/mxmCherry/openrtb/v14/openrtb2"
 	"github.com/prebid/prebid-server/stored_requests"
 
+	"github.com/gofrs/uuid"
 	"github.com/golang/glog"
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
@@ -61,6 +62,7 @@ type exchange struct {
 	UsersyncIfAmbiguous bool
 	privacyConfig       config.Privacy
 	categoriesFetcher   stored_requests.CategoryFetcher
+	bidIDGenerator      BidIDGenerator
 }
 
 // Container to pass out response ext data from the GetAllBids goroutines back into the main thread
@@ -79,6 +81,24 @@ type bidResponseWrapper struct {
 	bidder       openrtb_ext.BidderName
 }
 
+type BidIDGenerator interface {
+	New() (string, error)
+	Enabled() bool
+}
+
+type bidIDGenerator struct {
+	enabled bool
+}
+
+func (big *bidIDGenerator) Enabled() bool {
+	return big.enabled
+}
+
+func (big *bidIDGenerator) New() (string, error) {
+	rawUuid, err := uuid.NewV4()
+	return rawUuid.String(), err
+}
+
 func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
 	return &exchange{
 		adapterMap:          adapters,
@@ -96,6 +116,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid
 			GDPR: cfg.GDPR,
 			LMT:  cfg.LMT,
 		},
+		bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID},
 	}
 }
 
@@ -192,6 +213,17 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 			}
 		}
 
+		if e.bidIDGenerator.Enabled() {
+			for _, seatBid := range adapterBids {
+				for _, pbsBid := range seatBid.bids {
+					pbsBid.generatedBidID, err = e.bidIDGenerator.New()
+					if err != nil {
+						errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid"))
+					}
+				}
+			}
+		}
+
 		evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL)
 		adapterBids = evTracking.modifyBidsForEvents(adapterBids)
 
@@ -852,6 +884,7 @@ func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool
 			Targeting:         bid.bidTargets,
 			Type:              bid.bidType,
 			Video:             bid.bidVideo,
+			BidId:             bid.generatedBidID,
 		}
 
 		if cacheInfo, found := e.getBidCacheInfo(bid, auc); found {
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index 7d787cf8..535ac76b 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -21,6 +21,7 @@ type ExtBidPrebid struct {
 	Type              BidType             `json:"type"`
 	Video             *ExtBidPrebidVideo  `json:"video,omitempty"`
 	Events            *ExtBidPrebidEvents `json:"events,omitempty"`
+	BidId             string              `json:"bidid,omitempty"`
 }
 
 // ExtBidPrebidCache defines the contract for  bidresponse.seatbid.bid[i].ext.prebid.cache
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index e03c234b..4d95babc 100755
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -100,12 +100,14 @@ const (
 	BidderBeachfront       BidderName = "beachfront"
 	BidderBeintoo          BidderName = "beintoo"
 	BidderBetween          BidderName = "between"
+	BidderBidmachine       BidderName = "bidmachine"
 	BidderBrightroll       BidderName = "brightroll"
 	BidderColossus         BidderName = "colossus"
 	BidderConnectAd        BidderName = "connectad"
 	BidderConsumable       BidderName = "consumable"
 	BidderConversant       BidderName = "conversant"
 	BidderCpmstar          BidderName = "cpmstar"
+	BidderCriteo           BidderName = "criteo"
 	BidderDatablocks       BidderName = "datablocks"
 	BidderDmx              BidderName = "dmx"
 	BidderDecenterAds      BidderName = "decenterads"
@@ -208,12 +210,14 @@ func CoreBidderNames() []BidderName {
 		BidderBeachfront,
 		BidderBeintoo,
 		BidderBetween,
+		BidderBidmachine,
 		BidderBrightroll,
 		BidderColossus,
 		BidderConnectAd,
 		BidderConsumable,
 		BidderConversant,
 		BidderCpmstar,
+		BidderCriteo,
 		BidderDatablocks,
 		BidderDecenterAds,
 		BidderDeepintent,
diff --git a/openrtb_ext/imp_bidmachine.go b/openrtb_ext/imp_bidmachine.go
new file mode 100644
index 00000000..20491d25
--- /dev/null
+++ b/openrtb_ext/imp_bidmachine.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ExtImpBidmachine struct {
+	Host     string `json:"host"`
+	Path     string `json:"path"`
+	SellerID string `json:"seller_id"`
+}
diff --git a/openrtb_ext/imp_criteo.go b/openrtb_ext/imp_criteo.go
new file mode 100644
index 00000000..e200aace
--- /dev/null
+++ b/openrtb_ext/imp_criteo.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ExtImpCriteo defines the contract for bidrequest.imp[i].ext.criteo
+type ExtImpCriteo struct {
+	ZoneID    int64 `json:"zoneId"`
+	NetworkID int64 `json:"networkId"`
+}
diff --git a/static/bidder-info/bidmachine.yaml b/static/bidder-info/bidmachine.yaml
new file mode 100644
index 00000000..6868125b
--- /dev/null
+++ b/static/bidder-info/bidmachine.yaml
@@ -0,0 +1,8 @@
+maintainer:
+  email: hi@bidmachine.io
+gvlVendorID: 736
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
\ No newline at end of file
diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml
new file mode 100644
index 00000000..b27e1fae
--- /dev/null
+++ b/static/bidder-info/criteo.yaml
@@ -0,0 +1,10 @@
+maintainer:
+  email: "pi-direct@criteo.com"
+gvlVendorID: 91
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+  site:
+    mediaTypes:
+      - banner
\ No newline at end of file
diff --git a/static/bidder-params/bidmachine.json b/static/bidder-params/bidmachine.json
new file mode 100644
index 00000000..f3971c00
--- /dev/null
+++ b/static/bidder-params/bidmachine.json
@@ -0,0 +1,31 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Bidmachine Adapter Params",
+    "description": "A schema which validates params accepted by the Kidoz adapter",
+    "type": "object",
+    "properties": {
+        "host": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "Host"
+        },
+        "path": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "URL path"
+        },
+        "seller_id": {
+            "$ref": "#/definitions/non-empty-string",
+            "description": "Seller Identifier"
+        }
+    },
+    "definitions": {
+        "non-empty-string": {
+            "type": "string",
+            "minLength": 1
+        }
+    },
+    "required": [
+        "host",
+        "path",
+        "seller_id"
+    ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json
new file mode 100644
index 00000000..9d348a7e
--- /dev/null
+++ b/static/bidder-params/criteo.json
@@ -0,0 +1,30 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Criteo adapter params",
+    "description": "The schema to validate Criteo specific params accepted by Criteo adapter",
+    "type": "object",
+    "properties": {
+        "zoneid": {
+            "type": "number",
+            "description": "Impression's zone ID.",
+            "minimum": 0
+        },
+        "networkid": {
+            "type": "number",
+            "description": "Impression's network ID.",
+            "minimum": 0
+        }
+    },
+    "anyOf": [
+        {
+            "required": [
+                "zoneid"
+            ]
+        },
+        {
+            "required": [
+                "networkid"
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/rtbhouse.json b/static/bidder-params/rtbhouse.json
index e8f6bd03..00732bed 100644
--- a/static/bidder-params/rtbhouse.json
+++ b/static/bidder-params/rtbhouse.json
@@ -1,8 +1,13 @@
 {
-  "$schema": "http://json-schema.org/draft-04/schema#",
-  "title": "RTB House Adapter Params",
-  "description": "A schema which validates params accepted by the RTB House adapter",
-  "type": "object",
-  "properties": {},
-  "required": []
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "RTB House Adapter Params",
+    "description": "A schema which validates params accepted by the RTB House adapter",
+    "type": "object",
+    "properties": {
+        "publisherId": {
+            "type": "string",
+            "description": "The publisher’s ID provided by RTB House"
+        }
+    },
+    "required": ["publisherId"]
 }
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 7467eb26..f01e4e4b 100644
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -32,6 +32,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/consumable"
 	"github.com/prebid/prebid-server/adapters/conversant"
 	"github.com/prebid/prebid-server/adapters/cpmstar"
+	"github.com/prebid/prebid-server/adapters/criteo"
 	"github.com/prebid/prebid-server/adapters/datablocks"
 	"github.com/prebid/prebid-server/adapters/deepintent"
 	"github.com/prebid/prebid-server/adapters/dmx"
@@ -122,6 +123,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer)
+	insertIntoMap(cfg, syncers, openrtb_ext.BidderCriteo, criteo.NewCriteoSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer)
 	insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer)


Test patch:
diff --git a/adapters/bidmachine/bidmachine_test.go b/adapters/bidmachine/bidmachine_test.go
new file mode 100644
index 00000000..9dc82150
--- /dev/null
+++ b/adapters/bidmachine/bidmachine_test.go
@@ -0,0 +1,28 @@
+package bidmachine
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{
+		Endpoint: "https://{{.Host}}.bidmachine.io"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "bidmachinetest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderBidmachine, config.Adapter{
+		Endpoint: "{{Malformed}}"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json
new file mode 100644
index 00000000..96bf06be
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json
@@ -0,0 +1,128 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "instl": 1,
+            "banner":
+            {
+                "battr": [1],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "instl": 1,
+                    "banner":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json
new file mode 100644
index 00000000..08b187e5
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json
@@ -0,0 +1,128 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "instl": 1,
+            "banner":
+            {
+                "battr": [1, 16],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "instl": 1,
+                    "banner":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json
new file mode 100644
index 00000000..72bf59d7
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json
@@ -0,0 +1,132 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "video":
+            {
+                "battr": [1],
+                "mimes": [
+                    "video/mp4",
+                    "video/3gpp"
+                ],
+                "protocols": [
+                    1,
+                    2,
+                    3
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "video":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "video/mp4",
+                            "video/3gpp"
+                        ],
+                        "protocols": [
+                            1,
+                            2,
+                            3
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json
new file mode 100644
index 00000000..0d681e16
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json
@@ -0,0 +1,132 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "video":
+            {
+                "battr": [1, 16],
+                "mimes": [
+                    "video/mp4",
+                    "video/3gpp"
+                ],
+                "protocols": [
+                    1,
+                    2,
+                    3
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "prebid":
+                {
+                    "is_rewarded_inventory": 1
+                },
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "video":
+                    {
+                        "battr": [1, 16],
+                        "mimes": [
+                            "video/mp4",
+                            "video/3gpp"
+                        ],
+                        "protocols": [
+                            1,
+                            2,
+                            3
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "prebid":
+                        {
+                            "is_rewarded_inventory": 1
+                        },
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json
new file mode 100644
index 00000000..bde25edc
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json
@@ -0,0 +1,118 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "banner":
+            {
+                "battr": [1],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 50
+            },
+            "ext":
+            {
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "banner":
+                    {
+                        "battr": [1],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 50
+                    },
+                    "ext":
+                    {
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json
new file mode 100644
index 00000000..b49c85e5
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json
@@ -0,0 +1,120 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "instl": 1,
+            "banner":
+            {
+                "battr": [1],
+                "mimes": [
+                    "image/jpeg",
+                    "image/jpg",
+                    "image/gif",
+                    "image/png"
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "instl": 1,
+                    "banner":
+                    {
+                        "battr": [1],
+                        "mimes": [
+                            "image/jpeg",
+                            "image/jpg",
+                            "image/gif",
+                            "image/png"
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "banner"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json b/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json
new file mode 100644
index 00000000..e6e1d2dc
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/exemplary/simple_video.json
@@ -0,0 +1,124 @@
+{
+    "mockBidRequest":
+    {
+        "id": "test-request-id",
+        "imp": [
+        {
+            "id": "test-imp-id",
+            "video":
+            {
+                "battr": [1],
+                "mimes": [
+                    "video/mp4",
+                    "video/3gpp"
+                ],
+                "protocols": [
+                    1,
+                    2,
+                    3
+                ],
+                "w": 320,
+                "h": 480
+            },
+            "ext":
+            {
+                "bidder":
+                {
+                    "host": "api-us",
+                    "path": "auction/rtb/v2",
+                    "seller_id": "1"
+                }
+            }
+        }]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest":
+        {
+            "uri": "https://api-us.bidmachine.io/auction/rtb/v2/1",
+            "headers":
+            {
+                "Content-Type": [
+                    "application/json"
+                ],
+                "Accept": [
+                    "application/json"
+                ],
+                "X-Openrtb-Version": [
+                    "2.5"
+                ]
+            },
+            "body":
+            {
+                "id": "test-request-id",
+                "imp": [
+                {
+                    "id": "test-imp-id",
+                    "video":
+                    {
+                        "battr": [1],
+                        "mimes": [
+                            "video/mp4",
+                            "video/3gpp"
+                        ],
+                        "protocols": [
+                            1,
+                            2,
+                            3
+                        ],
+                        "w": 320,
+                        "h": 480
+                    },
+                    "ext":
+                    {
+                        "bidder":
+                        {
+                            "host": "api-us",
+                            "path": "auction/rtb/v2",
+                            "seller_id": "1"
+                        }
+                    }
+                }]
+            }
+        },
+        "mockResponse":
+        {
+            "status": 200,
+            "body":
+            {
+                "id": "test-request-id",
+                "seatbid": [
+                {
+                    "bid": [
+                    {
+                        "id": "test-bid-id",
+                        "impid": "test-imp-id",
+                        "price": 100,
+                        "adm": "test-adm",
+                        "cid": "test-cid",
+                        "crid": "test-crid"
+                    }],
+                    "seat": "bidmachine"
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+        {
+            "bid":
+            {
+                "id": "test-bid-id",
+                "impid": "test-imp-id",
+                "price": 100,
+                "adm": "test-adm",
+                "cid": "test-cid",
+                "crid": "test-crid"
+            },
+            "type": "video"
+        }]
+    }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/params/race/banner.json b/adapters/bidmachine/bidmachinetest/params/race/banner.json
new file mode 100644
index 00000000..0402e4a1
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/params/race/banner.json
@@ -0,0 +1,5 @@
+{
+  "seller_id": "1",
+  "path": "auction/rtb/v2",
+  "host": "api-eu"
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/params/race/video.json b/adapters/bidmachine/bidmachinetest/params/race/video.json
new file mode 100644
index 00000000..0402e4a1
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/params/race/video.json
@@ -0,0 +1,5 @@
+{
+  "seller_id": "1",
+  "path": "auction/rtb/v2",
+  "host": "api-eu"
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json b/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json
new file mode 100644
index 00000000..b454ce3d
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json
@@ -0,0 +1,19 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-impression-id-1",
+        "banner": {
+          "format": []
+        }
+      }
+    ]
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Impression with id: test-impression-id-1 has following error: Banner width and height is not provided and banner format array is empty. At least one is required",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json b/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json
new file mode 100644
index 00000000..563737e1
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json
@@ -0,0 +1,26 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {},
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "expectedMakeRequestsErrors": [
+  {
+    "value": "Impression with id: test-impression-id-1 has following error: Banner width and height is not provided and banner format is missing. At least one is required",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-204.json b/adapters/bidmachine/bidmachinetest/supplemental/status-204.json
new file mode 100644
index 00000000..daa7f00a
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-204.json
@@ -0,0 +1,66 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 204,
+      "body":
+      {}
+    }
+  }],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-400.json b/adapters/bidmachine/bidmachinetest/supplemental/status-400.json
new file mode 100644
index 00000000..5eb4ccfc
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-400.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 400,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 400 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-403.json b/adapters/bidmachine/bidmachinetest/supplemental/status-403.json
new file mode 100644
index 00000000..3434275b
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-403.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 403,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 403 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-408.json b/adapters/bidmachine/bidmachinetest/supplemental/status-408.json
new file mode 100644
index 00000000..51965461
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-408.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 408,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 408 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-500.json b/adapters/bidmachine/bidmachinetest/supplemental/status-500.json
new file mode 100644
index 00000000..f7abfb0f
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-500.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 500,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 500 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-502.json b/adapters/bidmachine/bidmachinetest/supplemental/status-502.json
new file mode 100644
index 00000000..44dabb05
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-502.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 502,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 502 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-503.json b/adapters/bidmachine/bidmachinetest/supplemental/status-503.json
new file mode 100644
index 00000000..979ac4f6
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-503.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 503,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 503 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/status-504.json b/adapters/bidmachine/bidmachinetest/supplemental/status-504.json
new file mode 100644
index 00000000..918ada2c
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/status-504.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest":
+  {
+    "id": "test-request-id",
+    "imp": [
+    {
+      "id": "test-impression-id-1",
+      "banner":
+      {
+        "format": [
+        {
+          "h": 250,
+          "w": 300
+        }]
+      },
+      "ext":
+      {
+        "bidder":
+        {
+          "seller_id": "1",
+          "host": "api-eu",
+          "path": "auction/rtb/v2"
+        }
+      }
+    }]
+  },
+  "httpCalls": [
+  {
+    "expectedRequest":
+    {
+      "uri": "https://api-eu.bidmachine.io/auction/rtb/v2/1",
+      "body":
+      {
+        "id": "test-request-id",
+        "imp": [
+        {
+          "id": "test-impression-id-1",
+          "banner":
+          {
+            "format": [
+            {
+              "h": 250,
+              "w": 300
+            }]
+          },
+          "ext":
+          {
+            "bidder":
+            {
+              "seller_id": "1",
+              "host": "api-eu",
+              "path": "auction/rtb/v2"
+            }
+          }
+        }]
+      }
+    },
+    "mockResponse":
+    {
+      "status": 504,
+      "body": "server text here"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+  {
+    "value": "unexpected status code: 504 \"server text here\"",
+    "comparison": "literal"
+  }]
+}
\ No newline at end of file
diff --git a/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json b/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json
new file mode 100644
index 00000000..82ee3269
--- /dev/null
+++ b/adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json
@@ -0,0 +1,35 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-impression-id-1",
+        "banner": {
+          "format": [
+            {
+              "w": 320,
+              "h": 50
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "host": "`",
+            "path": "auction/rtb/v2",
+            "seller_id": "1"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [],
+  "expectedBidResponses": [],
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Failed to create final URL with provided host",
+      "comparison": "literal"
+    }
+  ],
+  "expectedMakeBidsErrors": []
+}
+
diff --git a/adapters/bidmachine/params_test.go b/adapters/bidmachine/params_test.go
new file mode 100644
index 00000000..15ebbcbf
--- /dev/null
+++ b/adapters/bidmachine/params_test.go
@@ -0,0 +1,78 @@
+package bidmachine
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderBidmachine, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Bidmachine params: %s \n Error: %s", validParam, err)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderBidmachine, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"seller_id":"1", "host":"api-eu", "path":"auction/rtb/v2"}`,
+	`{"seller_id":"1", "host":"api-us", "path":"auction/rtb/v2"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"some_random_field":""}`,
+	`{"seller_id":""}`,
+	`{"seller_id": 1}`,
+	`{"seller_id": 1.2}`,
+	`{"seller_id": null}`,
+	`{"seller_id": true}`,
+	`{"seller_id": []}`,
+	`{"seller_id": {}}`,
+	`{"host":""}`,
+	`{"host": 1}`,
+	`{"host": 1.2}`,
+	`{"host": null}`,
+	`{"host": true}`,
+	`{"host": []}`,
+	`{"host": {}}`,
+	`{"path":""}`,
+	`{"path": 1}`,
+	`{"path": 1.2}`,
+	`{"path": null}`,
+	`{"path": true}`,
+	`{"path": []}`,
+	`{"path": {}}`,
+	`{"seller_id":"", "path": "", host: ""}`,
+	`{"seller_id": 1, "path": 2, host: 3}`,
+	`{"seller_id": 1.2}, "path": 5.5, host: 3.3`,
+	`{"seller_id": null, "path": null, host: null}`,
+	`{"seller_id": true, "path": false, host: true}`,
+	`{"seller_id": [], "path": [], host: []}`,
+	`{"seller_id": {}, "path": {}, host: {}}`,
+}
diff --git a/adapters/criteo/criteo_test.go b/adapters/criteo/criteo_test.go
new file mode 100644
index 00000000..0a700734
--- /dev/null
+++ b/adapters/criteo/criteo_test.go
@@ -0,0 +1,28 @@
+package criteo
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+
+	// Setup:
+	bidder, buildErr := builderWithGuidGenerator(
+		openrtb_ext.BidderCriteo,
+		config.Adapter{
+			Endpoint: "https://bidder.criteo.com/cdb?profileId=230",
+		},
+		newFakeGuidGenerator("00000000-0000-0000-00000000"),
+	)
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	// Execute & Verify:
+	adapterstest.RunJSONBidderTest(t, "criteotest", bidder)
+}
diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json
new file mode 100755
index 00000000..cb152f47
--- /dev/null
+++ b/adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json
@@ -0,0 +1,115 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "buyeruid": "criteo-user-id"
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"],
+          "Cookie": ["uid=criteo-user-id"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "cookieuid": "criteo-user-id",
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json
new file mode 100755
index 00000000..0ec1b7f8
--- /dev/null
+++ b/adapters/criteo/criteotest/exemplary/simple-banner-inapp.json
@@ -0,0 +1,110 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "test-request-id",
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+  
diff --git a/adapters/criteo/criteotest/exemplary/simple-banner-uid.json b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json
new file mode 100755
index 00000000..2499790b
--- /dev/null
+++ b/adapters/criteo/criteotest/exemplary/simple-banner-uid.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ip": "91.199.242.236",
+      "ua": "random user agent",
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-slot-id",
+            "impid": "test-imp-id",
+            "price": 0.1,
+            "crid": "creative-123",
+            "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/204-response-from-target.json b/adapters/criteo/criteotest/supplemental/204-response-from-target.json
new file mode 100755
index 00000000..9ac14ff6
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/204-response-from-target.json
@@ -0,0 +1,81 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/400-response-from-target.json b/adapters/criteo/criteotest/supplemental/400-response-from-target.json
new file mode 100755
index 00000000..ce3a4b62
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/400-response-from-target.json
@@ -0,0 +1,86 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 400
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": [
+      {
+        "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/500-response-from-target.json b/adapters/criteo/criteotest/supplemental/500-response-from-target.json
new file mode 100755
index 00000000..db6c16a3
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/500-response-from-target.json
@@ -0,0 +1,86 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 500
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": [
+      {
+        "value": "Unexpected status code: 500. Run with request.debug = 1 for more info",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..b047fa18
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json
@@ -0,0 +1,115 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": ["91.199.242.236"],
+            "User-Agent": ["random user agent"]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json
new file mode 100755
index 00000000..f839624a
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json
@@ -0,0 +1,139 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YYY"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YYY"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..77a37361
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json
@@ -0,0 +1,85 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "os": "android"
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YNN"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YNN",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json
new file mode 100755
index 00000000..31c14fc4
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "us_privacy": "1YNN"
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "uspIab": "1YNN"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..416053db
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json
@@ -0,0 +1,126 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithconsent"
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json
new file mode 100755
index 00000000..e8374616
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json
@@ -0,0 +1,142 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithconsent",
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json
new file mode 100755
index 00000000..7385b937
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json
@@ -0,0 +1,92 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ifa": "test-ifa-123456",
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithnoconsent"
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithnoconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json
new file mode 100755
index 00000000..dbc5fb32
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json
@@ -0,0 +1,108 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "consent": "iabconsentstringwithoutconsent",
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid"
+            },
+            "gdprconsent": {
+              "consentdata": "iabconsentstringwithoutconsent",
+              "gdprapplies": true
+            },
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 204
+        }
+      }
+    ],
+    "expectedBidResponses": []
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json
new file mode 100755
index 00000000..75f4118e
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json
@@ -0,0 +1,213 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "app": {
+        "bundle": "test.app.bundle"
+      },
+      "device": {
+        "ip": "91.199.242.236",
+        "ua": "random user agent",
+        "ifa": "test-ifa-123456",
+        "os": "android"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id-1",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-2",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 7891011,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-3",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 121314,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "bundleid": "test.app.bundle",
+              "networkid": 78910
+            },
+            "user": {
+              "deviceid": "test-ifa-123456",
+              "deviceos": "android",
+              "deviceidtype": "gaid",
+              "ip": "91.199.242.236",
+              "ua": "random user agent"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id-1",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-2",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "cpm": 0.2,
+                "currency": "USD",
+                "width": 320,
+                "height": 50,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-3",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "cpm": 0.3,
+                "currency": "USD",
+                "width": 300,
+                "height": 600,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id-1",
+              "impid": "test-imp-id-1",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-2",
+              "impid": "test-imp-id-2",
+              "price": 0.2,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 320,
+              "h": 50
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-3",
+              "impid": "test-imp-id-3",
+              "price": 0.3,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 600
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json
new file mode 100755
index 00000000..dcb51825
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json
@@ -0,0 +1,84 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id-1",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-2",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 7891011,
+              "networkid": 123456
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-3",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 121314,
+              "networkid": 467890
+            }
+          }
+        }
+      ]
+    },
+      "expectedMakeRequestsErrors": [
+        {
+          "value": "Bid request has slots coming with several network IDs which is not allowed",
+          "comparison": "literal"
+        }
+      ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json
new file mode 100755
index 00000000..e5de75e0
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json
@@ -0,0 +1,233 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "site": {
+        "id": "site-id",
+        "page": "criteo.com"
+      },
+      "device": {
+        "os": "android",
+        "ip": "91.199.242.236",
+        "ua": "random user agent"
+      },
+      "user": {
+        "ext": {
+          "eids": [{
+            "source": "criteo.com",
+            "uids": [{
+              "id": "criteo-eid"
+            }]
+          }]
+        }
+      },
+      "imp": [
+        {
+          "id": "test-imp-id-1",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 123456,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-2",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 7891011,
+              "networkid": 78910
+            }
+          }
+        },
+        {
+          "id": "test-imp-id-3",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "zoneid": 121314,
+              "networkid": 78910
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://bidder.criteo.com/cdb?profileId=230",
+          "headers": {
+            "X-Forwarded-For": [
+              "91.199.242.236"
+            ],
+            "User-Agent": [
+              "random user agent"
+            ]
+          },
+          "body": {
+            "id": "test-request-id",
+            "publisher": {
+              "siteid": "site-id",
+              "url": "criteo.com",
+              "networkid": 78910
+            },
+            "user": {
+              "ip": "91.199.242.236",
+              "ua": "random user agent",
+              "deviceos": "android",
+              "deviceidtype": "gaid"
+            },
+            "gdprconsent": {},
+            "slots": [
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              },
+              {
+                "slotid": "00000000-0000-0000-00000000",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "sizes": [
+                  "300x250"
+                ]
+              }
+            ],
+            "eids": [
+              {
+                "source": "criteo.com",
+                "uids": [
+                  {
+                    "id": "criteo-eid"
+                  }
+                ]
+              }
+            ]
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "slots": [
+              {
+                "id": "test-slot-id-1",
+                "impid": "test-imp-id-1",
+                "zoneid": 123456,
+                "networkid": 78910,
+                "cpm": 0.1,
+                "currency": "USD",
+                "width": 300,
+                "height": 250,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-2",
+                "impid": "test-imp-id-2",
+                "zoneid": 7891011,
+                "networkid": 78910,
+                "cpm": 0.2,
+                "currency": "USD",
+                "width": 320,
+                "height": 50,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              },
+              {
+                "id": "test-slot-id-3",
+                "impid": "test-imp-id-3",
+                "zoneid": 121314,
+                "networkid": 78910,
+                "cpm": 0.3,
+                "currency": "USD",
+                "width": 300,
+                "height": 600,
+                "creativeid": "creative-123",
+                "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id-1",
+              "impid": "test-imp-id-1",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-2",
+              "impid": "test-imp-id-2",
+              "price": 0.2,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 320,
+              "h": 50
+            },
+            "type": "banner"
+          },
+          {
+            "bid": {
+              "id": "test-slot-id-3",
+              "impid": "test-imp-id-3",
+              "price": 0.3,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 600
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json
new file mode 100755
index 00000000..a0cc53d0
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 250,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "250x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json
new file mode 100755
index 00000000..016c16a8
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json
@@ -0,0 +1,132 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json
new file mode 100755
index 00000000..68e45da9
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ip": "91.199.242.236",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["91.199.242.236"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ip": "91.199.242.236",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json
new file mode 100755
index 00000000..e248db9b
--- /dev/null
+++ b/adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "site": {
+      "id": "site-id",
+      "page": "criteo.com"
+    },
+    "device": {
+      "os": "android",
+      "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000",
+      "ua": "random user agent"
+    },
+    "user": {
+      "ext": {
+        "eids": [{
+          "source": "criteo.com",
+          "uids": [{
+            "id": "criteo-eid"
+          }]
+        }]
+      }
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+          "bidder": {
+            "zoneid": 123456,
+            "networkid": 78910
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://bidder.criteo.com/cdb?profileId=230",
+        "headers": {
+          "X-Forwarded-For": ["fd36:ce97:0fa1:dec0:0000:0000:0000:0000"],
+          "User-Agent": ["random user agent"]
+        },
+        "body": {
+          "id": "test-request-id",
+          "publisher": {
+            "siteid": "site-id",
+            "url": "criteo.com",
+            "networkid": 78910
+          },
+          "user": {
+            "ipv6": "fd36:ce97:0fa1:dec0:0000:0000:0000:0000",
+            "ua": "random user agent",
+            "deviceos": "android",
+            "deviceidtype": "gaid"
+          },
+          "gdprconsent": {},
+          "slots": [
+            {
+              "slotid": "00000000-0000-0000-00000000",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "sizes": [
+                "300x250"
+              ]
+            }
+          ],
+          "eids": [
+            {
+              "source": "criteo.com",
+              "uids": [
+                {
+                  "id": "criteo-eid"
+                }
+              ]
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "slots": [
+            {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "zoneid": 123456,
+              "networkid": 78910,
+              "cpm": 0.1,
+              "currency": "USD",
+              "width": 300,
+              "height": 250,
+              "creativeid": "creative-123",
+              "creative": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>"
+            }
+          ]
+        }
+      }
+    }
+  ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "test-slot-id",
+              "impid": "test-imp-id",
+              "price": 0.1,
+              "crid": "creative-123",
+              "adm": "<iframe id='789abc' name='789abc' src='http://creative-url.criteo.com'></iframe>",
+              "w": 300,
+              "h": 250
+            },
+            "type": "banner"
+          }
+        ]
+      }
+  ]
+}
+
diff --git a/adapters/criteo/models_test.go b/adapters/criteo/models_test.go
new file mode 100644
index 00000000..9da7bda2
--- /dev/null
+++ b/adapters/criteo/models_test.go
@@ -0,0 +1,452 @@
+package criteo
+
+import (
+	"encoding/json"
+	"reflect"
+	"testing"
+
+	"github.com/mxmCherry/openrtb/v14/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestGetDeviceType(t *testing.T) {
+
+	// Setup:
+	deviceTypeCases := []struct {
+		deviceType string
+		expected   string
+	}{
+		{"ios", "idfa"},
+		{"Ios", "idfa"},
+		{"IOS", "idfa"},
+		{"android", "gaid"},
+		{"unknown", "unknown"},
+		{"", "unknown"},
+		{"qwerty", "unknown"},
+		{"qWerty", "unknown"},
+		{"abc", "unknown"},
+	}
+
+	for _, uc := range deviceTypeCases {
+		// Execute:
+		result := getDeviceType(uc.deviceType)
+
+		// Verify:
+		if uc.expected != result {
+			t.Errorf("Bad getDeviceType for '%s'. Expected: %s, got %s", uc.deviceType, uc.expected, result)
+		}
+	}
+}
+
+func TestNewCriteoRequest(t *testing.T) {
+	// Setup:
+	var (
+		dummyRequestID         = "random request ID"
+		dummyPublisherBundleID = "bundleid"
+		dummyPublisherURL      = "test.com"
+		dummyPublisherSiteID   = "siteid"
+		dummyUserDeviceID      = "random-device-id"
+		dummyUserDeviceOS      = "android"
+		dummyUserDeviceIDType  = "gaid"
+		dummyUserCookieID      = "random-cookie-id"
+		dummyUserIP            = "1.1.1.1"
+		dummyUserUA            = "random UA"
+		dummyGdprApplies       = true
+		dummyGdprAppliesUint   = int8(1)
+		dummyGdprConsentData   = "randomconsentdata"
+		dummySlotID            = "11111111-1111-1111-11111111"
+		dummySlotImpID         = "fake-imp-id-1"
+		dummySlotZoneID        = int64(1)
+	)
+
+	fakeSlotIDGenerator := newFakeGuidGenerator(dummySlotID)
+
+	// The request doesn't make any sense but aims to fill every single criteo request fields
+	expectedCriteoRequest := criteoRequest{
+		ID: dummyRequestID,
+		Publisher: criteoPublisher{
+			SiteID:   dummyPublisherSiteID,
+			BundleID: dummyPublisherBundleID,
+			URL:      dummyPublisherURL,
+		},
+		User: criteoUser{
+			DeviceID:     dummyUserDeviceID,
+			DeviceOS:     dummyUserDeviceOS,
+			DeviceIDType: dummyUserDeviceIDType,
+			CookieID:     dummyUserCookieID,
+			IP:           dummyUserIP,
+			UA:           dummyUserUA,
+		},
+		GdprConsent: criteoGdprConsent{
+			GdprApplies: &dummyGdprApplies,
+			ConsentData: dummyGdprConsentData,
+		},
+		Slots: []criteoRequestSlot{
+			{
+				SlotID: dummySlotID,
+				ImpID:  dummySlotImpID,
+				ZoneID: &dummySlotZoneID,
+			},
+		},
+	}
+
+	userExtJSON, _ := json.Marshal(&openrtb_ext.ExtUser{
+		Consent: dummyGdprConsentData,
+	})
+	regsExtJSON, _ := json.Marshal(&openrtb_ext.ExtRegs{
+		GDPR: &dummyGdprAppliesUint,
+	})
+	bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{
+		ZoneID: dummySlotZoneID,
+	})
+	impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{
+		Bidder: bidderExtJSON,
+	})
+	incomingRequest := &openrtb2.BidRequest{
+		ID: dummyRequestID,
+		App: &openrtb2.App{
+			Bundle: dummyPublisherBundleID,
+		},
+		Site: &openrtb2.Site{
+			ID:   dummyPublisherSiteID,
+			Page: dummyPublisherURL,
+		},
+		User: &openrtb2.User{
+			BuyerUID: dummyUserCookieID,
+			Ext:      userExtJSON,
+		},
+		Regs: &openrtb2.Regs{
+			Ext: regsExtJSON,
+		},
+		Device: &openrtb2.Device{
+			IFA: dummyUserDeviceID,
+			OS:  dummyUserDeviceOS,
+			IP:  dummyUserIP,
+			UA:  dummyUserUA,
+		},
+		Imp: []openrtb2.Imp{
+			{
+				ID:  dummySlotImpID,
+				Ext: impExtJSON,
+			},
+		},
+	}
+
+	// Execute:
+	result, err := newCriteoRequest(fakeSlotIDGenerator, incomingRequest)
+
+	// Verify:
+	if err != nil {
+		t.Errorf("newCriteoRequest has errors: %s", err)
+	}
+
+	if expectedCriteoRequest.ID != result.ID ||
+		!reflect.DeepEqual(expectedCriteoRequest, result) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Publisher, result.Publisher) ||
+		!reflect.DeepEqual(expectedCriteoRequest.User, result.User) ||
+		!reflect.DeepEqual(expectedCriteoRequest.GdprConsent, result.GdprConsent) ||
+		len(expectedCriteoRequest.Slots) != len(result.Slots) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Slots[0], result.Slots[0]) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetGdprConsent(t *testing.T) {
+	// Setup:
+	var (
+		dummyGdprApplies     = true
+		dummyGdprConsentData = "randomconsentdata"
+		dummyGdprAppliesUint = int8(1)
+	)
+
+	expectedCriteoRequest := criteoRequest{
+		GdprConsent: criteoGdprConsent{
+			GdprApplies: &dummyGdprApplies,
+			ConsentData: dummyGdprConsentData,
+		},
+	}
+
+	userExtJSON, _ := json.Marshal(&openrtb_ext.ExtUser{
+		Consent: dummyGdprConsentData,
+	})
+	regsExtJSON, _ := json.Marshal(&openrtb_ext.ExtRegs{
+		GDPR: &dummyGdprAppliesUint,
+	})
+	incomingRequest := &openrtb2.BidRequest{
+		User: &openrtb2.User{
+			Ext: userExtJSON,
+		},
+		Regs: &openrtb2.Regs{
+			Ext: regsExtJSON,
+		},
+	}
+
+	var regsExt *openrtb_ext.ExtRegs
+	if incomingRequest.Regs != nil {
+		json.Unmarshal(incomingRequest.Regs.Ext, &regsExt)
+	}
+
+	// Execute:
+	gdprConsent, _ := newCriteoGdprConsent(incomingRequest.User, regsExt)
+	result := criteoRequest{
+		GdprConsent: gdprConsent,
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest, result) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getGdprConsent was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetUser(t *testing.T) {
+	// Setup:
+	var (
+		dummyUserDeviceID     = "random-device-id"
+		dummyUserDeviceOS     = "android"
+		dummyUserDeviceIDType = "gaid"
+		dummyUserCookieID     = "random-cookie-id"
+		dummyUserIP           = "1.1.1.1"
+		dummyUserUA           = "random UA"
+		dummyCcpaString       = "1YYY"
+	)
+	expectedCriteoRequest := &criteoRequest{
+		User: criteoUser{
+			DeviceID:     dummyUserDeviceID,
+			DeviceOS:     dummyUserDeviceOS,
+			DeviceIDType: dummyUserDeviceIDType,
+			CookieID:     dummyUserCookieID,
+			IP:           dummyUserIP,
+			UA:           dummyUserUA,
+			UspIab:       dummyCcpaString,
+		},
+	}
+
+	regsExt := &openrtb_ext.ExtRegs{
+		USPrivacy: dummyCcpaString,
+	}
+	regsExtData, err := json.Marshal(regsExt)
+	if err != nil {
+		t.Errorf("cannot marshal regsExt data")
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		User: &openrtb2.User{
+			BuyerUID: dummyUserCookieID,
+		},
+		Device: &openrtb2.Device{
+			IFA: dummyUserDeviceID,
+			OS:  dummyUserDeviceOS,
+			IP:  dummyUserIP,
+			UA:  dummyUserUA,
+		},
+		Regs: &openrtb2.Regs{
+			Ext: regsExtData,
+		},
+	}
+
+	// Execute:
+	result := &criteoRequest{
+		User: newCriteoUser(incomingRequest.User, incomingRequest.Device, regsExt),
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest.User, result.User) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getUser was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetUser_NilUserAndDevice(t *testing.T) {
+	// Setup:
+	var dummyCcpaString = "1YYY"
+
+	expectedCriteoRequest := &criteoRequest{}
+
+	regsExt := &openrtb_ext.ExtRegs{
+		USPrivacy: dummyCcpaString,
+	}
+	regsExtData, err := json.Marshal(regsExt)
+	if err != nil {
+		t.Errorf("cannot marshal regsExt data")
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		Regs: &openrtb2.Regs{
+			Ext: regsExtData,
+		},
+	}
+
+	// Execute:
+	result := &criteoRequest{
+		User: newCriteoUser(incomingRequest.User, incomingRequest.Device, regsExt),
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest.User, result.User) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getUser was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestPublisher(t *testing.T) {
+	// Setup:
+	var (
+		dummyPublisherSiteID         = "siteid"
+		dummyPublisherBundleID       = "bundleid"
+		dummyPublisherURL            = "test.com"
+		dummyNetworkID         int64 = 1234567
+	)
+	expectedCriteoRequest := &criteoRequest{
+		Publisher: criteoPublisher{
+			SiteID:    dummyPublisherSiteID,
+			BundleID:  dummyPublisherBundleID,
+			URL:       dummyPublisherURL,
+			NetworkID: &dummyNetworkID,
+		},
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		App: &openrtb2.App{
+			Bundle: dummyPublisherBundleID,
+		},
+		Site: &openrtb2.Site{
+			ID:   dummyPublisherSiteID,
+			Page: dummyPublisherURL,
+		},
+	}
+
+	// Execute:
+	result := &criteoRequest{
+		Publisher: newCriteoPublisher(&dummyNetworkID, incomingRequest.App, incomingRequest.Site),
+	}
+
+	// Verify:
+	if !reflect.DeepEqual(expectedCriteoRequest.Publisher, result.Publisher) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("getPublisher was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetRequestSlots(t *testing.T) {
+	// Setup:
+	var (
+		dummySlotImpID  = "fake-imp-id-1"
+		dummySlotZoneID = int64(1)
+		dummySlotID     = "22222222-2222-2222-22222222"
+	)
+
+	fakeSlotIDGenerator := newFakeGuidGenerator(dummySlotID)
+
+	expectedCriteoRequest := &criteoRequest{
+		Slots: []criteoRequestSlot{
+			{
+				SlotID: dummySlotID,
+				ImpID:  dummySlotImpID,
+				ZoneID: &dummySlotZoneID,
+			},
+		},
+	}
+
+	bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{
+		ZoneID: dummySlotZoneID,
+	})
+	impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{
+		Bidder: bidderExtJSON,
+	})
+	incomingRequest := &openrtb2.BidRequest{
+		Imp: []openrtb2.Imp{
+			{
+				ID:  dummySlotImpID,
+				Ext: impExtJSON,
+			},
+		},
+	}
+
+	// Execute:
+	slots, err := newCriteoRequestSlots(fakeSlotIDGenerator, incomingRequest.Imp)
+	result := &criteoRequest{
+		Slots: slots,
+	}
+
+	// Verify:
+	if err != nil {
+		t.Errorf("newCriteoRequestSlots has errors: %s", err)
+	}
+
+	if len(expectedCriteoRequest.Slots) != len(result.Slots) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Slots[0], result.Slots[0]) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
+
+func TestGetRequestMultipleSlots(t *testing.T) {
+	// Setup:
+	dummySlots := []struct {
+		ID     string
+		ZoneID int64
+	}{
+		{"fake-imp-id-1", 1},
+		{"fake-imp-id-2", 2},
+		{"fake-imp-id-3", 3},
+		{"fake-imp-id-4", 4},
+		{"fake-imp-id-5", 5},
+	}
+
+	incomingRequest := &openrtb2.BidRequest{
+		Imp: make([]openrtb2.Imp, len(dummySlots)),
+	}
+	slots := make([]criteoRequestSlot, len(dummySlots))
+
+	for i := range dummySlots {
+		// Build expected slots
+		slots[i] = criteoRequestSlot{
+			ImpID:  dummySlots[i].ID,
+			ZoneID: &dummySlots[i].ZoneID,
+		}
+
+		// Build incoming request imps
+		bidderExtJSON, _ := json.Marshal(&openrtb_ext.ExtImpCriteo{
+			ZoneID: dummySlots[i].ZoneID,
+		})
+		impExtJSON, _ := json.Marshal(&adapters.ExtImpBidder{
+			Bidder: bidderExtJSON,
+		})
+		incomingRequest.Imp[i] = openrtb2.Imp{
+			ID:  dummySlots[i].ID,
+			Ext: impExtJSON,
+		}
+	}
+
+	expectedCriteoRequestSlots, err := newCriteoRequestSlots(newFakeGuidGenerator(""), incomingRequest.Imp)
+	expectedCriteoRequest := &criteoRequest{
+		Slots: expectedCriteoRequestSlots,
+	}
+
+	// Execute:
+	slotsResult, err := newCriteoRequestSlots(newFakeGuidGenerator(""), incomingRequest.Imp)
+	result := &criteoRequest{
+		Slots: slotsResult,
+	}
+
+	// Verify:
+	if err != nil {
+		t.Errorf("newCriteoRequestSlots has errors: %s", err)
+	}
+
+	if len(expectedCriteoRequest.Slots) != len(result.Slots) ||
+		!reflect.DeepEqual(expectedCriteoRequest.Slots, result.Slots) {
+		actualResultJSON, _ := json.Marshal(result)
+		expectedResultJSON, _ := json.Marshal(expectedCriteoRequest)
+		t.Errorf("newCriteoRequest was incorrect, got '%s', want '%s'.", actualResultJSON, expectedResultJSON)
+	}
+}
diff --git a/adapters/criteo/params_test.go b/adapters/criteo/params_test.go
new file mode 100644
index 00000000..73ace617
--- /dev/null
+++ b/adapters/criteo/params_test.go
@@ -0,0 +1,63 @@
+package criteo
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/criteo.json
+//
+// These also validate the format of the external API: request.imp[i].ext.criteo
+
+// TestValidParams makes sure that the criteo schema accepts all imp.ext fields which we intend to support.
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderCriteo, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected criteo params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the criteo schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderCriteo, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"zoneid": 123456}`,
+	`{"networkid": 78910}`,
+	`{"zoneid": 123456, "networkid": 78910}`,
+	`{"zoneid": 0, "networkid": 0}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"zoneid": -123}`,
+	`{"networkid": -321}`,
+	`{"zoneid": -123, "networkid": -321}`,
+	`{"zoneid": -1}`,
+	`{"networkid": -1}`,
+	`{"zoneid": -1, "networkid": -1}`,
+}
diff --git a/adapters/criteo/usersync_test.go b/adapters/criteo/usersync_test.go
new file mode 100644
index 00000000..9010d8bb
--- /dev/null
+++ b/adapters/criteo/usersync_test.go
@@ -0,0 +1,26 @@
+package criteo
+
+import (
+	"testing"
+	"text/template"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/privacy"
+	"github.com/prebid/prebid-server/privacy/gdpr"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCriteoSyncer(t *testing.T) {
+	url := "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID"
+	temp := template.Must(template.New("sync-template").Parse(url))
+	syncer := NewCriteoSyncer(temp)
+	info, err := syncer.GetUsersyncInfo(privacy.Policies{
+		GDPR: gdpr.Policy{
+			Signal: "1",
+		},
+	})
+	assert.NoError(t, err)
+	assert.EqualValues(t, adapters.SyncTypeRedirect, info.Type)
+	assert.Equal(t, false, info.SupportCORS)
+	assert.Equal(t, "//prebidserver/getuid?https%3A%2F%2Fprebidserver%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dcriteo%26gdpr%3D1%26gdpr_consent%3D%26uid%3D%24UID", info.URL)
+}
diff --git a/adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json b/adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json
new file mode 100644
index 00000000..2fcf9179
--- /dev/null
+++ b/adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json
@@ -0,0 +1,148 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "app":{
+      "bundle":"302324249",
+      "name":"Skout - iOS Match Buy",
+      "publisher":{
+        "id":"10400"
+      },
+      "storeurl":"https://itunes.apple.com/app/id302324249"
+    },
+    "device": {
+      "ifa": "ed6207cefff74c14878963566683c070"
+    },
+    "imp": [
+      {
+
+        "id": "test-imp-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "dmxid": "123454",
+            "publisher_id": "10400"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "",
+        "body": {
+          "id": "test-request-id",
+          "app":{
+            "bundle":"302324249",
+            "id":"ed6207cefff74c14878963566683c070",
+            "name":"Skout - iOS Match Buy",
+            "publisher":{
+              "id":"10400",
+              "ext": {
+                "dmx": {
+                  "id": "10400"
+                }
+              }
+            },
+            "storeurl":"https://itunes.apple.com/app/id302324249"
+          },
+          "device": {
+            "ifa": "ed6207cefff74c14878963566683c070"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "tagid": "123454",
+              "secure": 1,
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "publisher_id": "10400",
+                  "dmxid": "123454"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [{
+                "id": "7706636740145184841",
+                "impid": "test-imp-id",
+                "price": 1.75,
+                "adid": "29681110",
+                "adm": "<div>banner-ads</div>",
+                "adomain": ["dmx.districtm.io"],
+                "iurl": "https://dmx.districtm.io/b/v2",
+                "cid": "958",
+                "crid": "29681110",
+                "h": 250,
+                "w": 300
+              }]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 1.75,
+            "adm": "<div>banner-ads</div>",
+            "adid": "29681110",
+            "adomain": ["dmx.districtm.io"],
+            "iurl": "https://dmx.districtm.io/b/v2",
+            "cid": "958",
+            "crid": "29681110",
+            "w": 300,
+            "h": 250
+
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/dmx/dmxtest/exemplary/missing-width-height.json b/adapters/dmx/dmxtest/exemplary/missing-width-height.json
new file mode 100644
index 00000000..b7eeec68
--- /dev/null
+++ b/adapters/dmx/dmxtest/exemplary/missing-width-height.json
@@ -0,0 +1,143 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "app":{
+      "bundle":"302324249",
+      "id":"ed6207cefff74c14878963566683c070",
+      "name":"Skout - iOS Match Buy",
+      "publisher":{
+        "id":"10400"
+      },
+      "storeurl":"https://itunes.apple.com/app/id302324249"
+    },
+    "imp": [
+      {
+        "bidfloor": 0.35,
+        "id": "test-imp-id",
+        "banner": {
+
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "dmxid": "123454",
+            "publisher_id": "10400"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "",
+        "body": {
+          "id": "test-request-id",
+          "app":{
+            "bundle":"302324249",
+            "id":"ed6207cefff74c14878963566683c070",
+            "name":"Skout - iOS Match Buy",
+            "publisher":{
+              "id":"10400",
+              "ext": {
+                "dmx": {
+                  "id": "10400"
+                }
+              }
+            },
+            "storeurl":"https://itunes.apple.com/app/id302324249"
+          },
+          "imp": [
+            {
+              "bidfloor": 0.35,
+              "id": "test-imp-id",
+              "tagid": "123454",
+              "secure": 1,
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "publisher_id": "10400",
+                  "dmxid": "123454"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "958",
+              "bid": [{
+                "id": "7706636740145184841",
+                "impid": "test-imp-id",
+                "price": 1.75,
+                "adid": "29681110",
+                "adm": "<div>banner-ads</div>",
+                "adomain": ["dmx.districtm.io"],
+                "iurl": "https://dmx.districtm.io/b/v2",
+                "cid": "958",
+                "crid": "29681110",
+                "h": 250,
+                "w": 300
+              }]
+            }
+          ],
+          "bidid": "5778926625248726496",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "7706636740145184841",
+            "impid": "test-imp-id",
+            "price": 1.75,
+            "adm": "<div>banner-ads</div>",
+            "adid": "29681110",
+            "adomain": ["dmx.districtm.io"],
+            "iurl": "https://dmx.districtm.io/b/v2",
+            "cid": "958",
+            "crid": "29681110",
+            "w": 300,
+            "h": 250
+
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go
index 18719086..10e57d12 100644
--- a/adapters/tappx/tappx_test.go
+++ b/adapters/tappx/tappx_test.go
@@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) {
 
 	url, err := bidderTappx.buildEndpointURL(&tappxExt, test)
 
-	match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url)
+	match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.2`, url)
 	if err != nil {
 		t.Errorf("Error while running regex validation: %s", err.Error())
 		return
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json
new file mode 100644
index 00000000..3c3037af
--- /dev/null
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json
@@ -0,0 +1,116 @@
+{
+  "mockBidRequest": {
+    "id": "0000000000001",
+    "test": 1,
+    "imp": [
+      {
+        "id": "adunit-1",
+        "banner": {
+          "w": 300,
+          "h": 250
+        },
+        "ext": {
+         "bidder": {
+            "tappxkey": "pub-12345-android-9876",
+            "endpoint": "ZZ123456PS",
+            "host": "ZZ123456PS.ssp.tappx.com/rtb/"
+          }
+        }
+      }
+    ],
+    "app": {
+      "id": "app_001",
+      "bundle": "com.rovio.angrybirds",
+      "publisher": {
+        "id": "2"
+      }
+    },
+    "user": {
+      "buyeruid": "A-38327932832"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
+        "body": {
+          "id": "0000000000001",
+          "test": 1,
+          "imp": [
+            {
+              "id": "adunit-1",
+              "banner": {
+                "w": 300,
+                "h": 250
+              },
+              "ext": {
+                "bidder": {
+                  "tappxkey": "pub-12345-android-9876",
+                  "endpoint": "ZZ123456PS",
+                 "host": "ZZ123456PS.ssp.tappx.com/rtb/"
+                }
+              }
+            }
+          ],
+          "app": {
+            "bundle": "com.rovio.angrybirds",
+            "id": "app_001",
+            "publisher": {
+              "id": "2"
+            }
+          },
+          "user": {
+            "buyeruid": "A-38327932832"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4",
+          "seatbid": [{
+              "bid": [{
+                  "id": "wehM-93KGr0_0_0",
+                  "impid": "adunit-1",
+                  "price": 0.5,
+                  "cid": "3706",
+                  "crid": "19005",
+                  "adid": "19005",
+                  "adm": "<!-- admarkup -->",
+                  "cat": ["IAB2"],
+                  "adomain": ["test.com"],
+                  "h": 250,
+                  "w": 300
+              }]
+          }],
+          "bidid": "wehM-93KGr0"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "wehM-93KGr0_0_0",
+            "impid": "adunit-1",
+            "price": 0.5,
+            "adm": "<!-- admarkup -->",
+            "adid": "19005",
+            "adomain": ["test.com"],
+            "cid": "3706",
+            "crid": "19005",
+            "w": 300,
+            "h": 250,
+            "cat": ["IAB2"]
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
index 4f5b792f..54f472d9 100644
--- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json
@@ -33,7 +33,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json
index ef61b0e2..58490233 100644
--- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json
+++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json
@@ -37,7 +37,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json
index 7a469b07..d6ce0554 100644
--- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json
+++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json
@@ -35,7 +35,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json
index 9ee2f8f4..f151151e 100644
--- a/adapters/tappx/tappxtest/exemplary/single-video-site.json
+++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json
@@ -39,7 +39,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json
index 6172da59..1c72cc90 100644
--- a/adapters/tappx/tappxtest/supplemental/204status.json
+++ b/adapters/tappx/tappxtest/supplemental/204status.json
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json
index 48c1b0fb..093f77ad 100644
--- a/adapters/tappx/tappxtest/supplemental/bidfloor.json
+++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json
@@ -34,7 +34,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json
index 04569504..a80a5eaa 100644
--- a/adapters/tappx/tappxtest/supplemental/http-err-status.json
+++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json
index 0cb5fd4a..41dcc26d 100644
--- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json
+++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json
@@ -30,7 +30,7 @@
   "httpCalls": [
     {
       "expectedRequest": {
-        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1",
+        "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2",
         "body": {
           "id": "0000000000001",
           "test": 1,
diff --git a/config/config_test.go b/config/config_test.go
index 7ff5f0fa..43ee8fa2 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -139,6 +139,7 @@ func TestDefaults(t *testing.T) {
 	cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled)
 	cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path)
 	cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true)
+	cmpBools(t, "generate_bid_id", cfg.GenerateBidID, false)
 }
 
 var fullConfig = []byte(`
@@ -230,6 +231,7 @@ certificates_file: /etc/ssl/cert.pem
 request_validation:
     ipv4_private_networks: ["1.1.1.0/24"]
     ipv6_private_networks: ["1111::/16", "2222::/16"]
+generate_bid_id: true
 `)
 
 var adapterExtraInfoConfig = []byte(`
@@ -415,6 +417,7 @@ func TestFullConfig(t *testing.T) {
 	cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24")
 	cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16")
 	cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16")
+	cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true)
 }
 
 func TestUnmarshalAdapterExtraInfo(t *testing.T) {
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 8cc5b5e9..41ca420a 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -71,7 +71,6 @@ func TestSingleBidder(t *testing.T) {
 	ctx = context.WithValue(ctx, DebugContextKey, true)
 
 	for _, test := range testCases {
-
 		mockBidderResponse := &adapters.BidderResponse{
 			Bids: []*adapters.TypedBid{
 				{
@@ -142,6 +141,48 @@ func TestSingleBidder(t *testing.T) {
 	}
 }
 
+func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
+	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
+	defer server.Close()
+
+	requestHeaders := http.Header{}
+	requestHeaders.Add("Content-Type", "application/json")
+	requestHeaders.Add("Authorization", "anySecret")
+
+	bidderImpl := &goodSingleBidder{
+		httpRequest: &adapters.RequestData{
+			Method:  "POST",
+			Uri:     server.URL,
+			Body:    []byte("requestJson"),
+			Headers: requestHeaders,
+		},
+		bidResponse: &adapters.BidderResponse{
+			Bids: []*adapters.TypedBid{},
+		},
+	}
+
+	debugInfo := &config.DebugInfo{Allow: true}
+	ctx := context.Background()
+	ctx = context.WithValue(ctx, DebugContextKey, true)
+
+	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
+	seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true)
+
+	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
+		{
+			Uri:            server.URL,
+			RequestBody:    "requestJson",
+			RequestHeaders: map[string][]string{"Content-Type": {"application/json"}},
+			ResponseBody:   "responseJson",
+			Status:         200,
+		},
+	}
+
+	assert.Empty(t, errs)
+	assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls)
+}
+
 // TestMultiBidder makes sure all the requests get sent, and the responses processed.
 // Because this is done in parallel, it should be run under the race detector.
 func TestMultiBidder(t *testing.T) {
@@ -891,86 +932,204 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 	}
 }
 
-// TestBadResponseLogging makes sure that openrtb_ext works properly on malformed HTTP requests.
-func TestBadRequestLogging(t *testing.T) {
-	info := &httpCallInfo{
-		err: errors.New("Bad request"),
-	}
-	ext := makeExt(info)
-	if ext.Uri != "" {
-		t.Errorf("The URI should be empty. Got %s", ext.Uri)
-	}
-	if ext.RequestBody != "" {
-		t.Errorf("The request body should be empty. Got %s", ext.RequestBody)
-	}
-	if ext.ResponseBody != "" {
-		t.Errorf("The response body should be empty. Got %s", ext.ResponseBody)
-	}
-	if ext.Status != 0 {
-		t.Errorf("The Status code should be 0. Got %d", ext.Status)
-	}
-	if len(ext.RequestHeaders) > 0 {
-		t.Errorf("The request headers should be empty. Got %s", ext.RequestHeaders)
-	}
-}
-
-// TestBadResponseLogging makes sure that openrtb_ext works properly if we don't get a sensible HTTP response.
-func TestBadResponseLogging(t *testing.T) {
-	info := &httpCallInfo{
-		request: &adapters.RequestData{
-			Uri:  "test.com",
-			Body: []byte("request body"),
-			Headers: http.Header{
-				"header-1": []string{"value-1"},
+func TestMakeExt(t *testing.T) {
+	testCases := []struct {
+		description string
+		given       *httpCallInfo
+		expected    *openrtb_ext.ExtHttpCall
+	}{
+		{
+			description: "Nil",
+			given:       nil,
+			expected:    &openrtb_ext.ExtHttpCall{},
+		},
+		{
+			description: "Empty",
+			given: &httpCallInfo{
+				err:      nil,
+				response: nil,
+				request:  nil,
 			},
+			expected: &openrtb_ext.ExtHttpCall{},
+		},
+		{
+			description: "Request & Response - No Error",
+			given: &httpCallInfo{
+				err: nil,
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
+				},
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+				ResponseBody:   "responseBody",
+				Status:         999,
+			},
+		},
+		{
+			description: "Request & Response - No Error with Authorization removal",
+			given: &httpCallInfo{
+				err: nil,
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}, "Authorization": {"secret"}}),
+				},
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+				ResponseBody:   "responseBody",
+				Status:         999,
+			},
+		},
+		{
+			description: "Request & Response - No Error with nil header",
+			given: &httpCallInfo{
+				err: nil,
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: nil,
+				},
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: nil,
+				ResponseBody:   "responseBody",
+				Status:         999,
+			},
+		},
+		{
+			description: "Request & Response - Error",
+			given: &httpCallInfo{
+				err: errors.New("error"),
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
+				},
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+			},
+		},
+		{
+			description: "Request Only",
+			given: &httpCallInfo{
+				err: nil,
+				request: &adapters.RequestData{
+					Uri:     "requestUri",
+					Body:    []byte("requestBody"),
+					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
+				},
+				response: nil,
+			},
+			expected: &openrtb_ext.ExtHttpCall{
+				Uri:            "requestUri",
+				RequestBody:    "requestBody",
+				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
+			},
+		}, {
+			description: "Response Only",
+			given: &httpCallInfo{
+				err: nil,
+				response: &adapters.ResponseData{
+					Body:       []byte("responseBody"),
+					StatusCode: 999,
+				},
+			},
+			expected: &openrtb_ext.ExtHttpCall{},
 		},
-		err: errors.New("Bad response"),
-	}
-	ext := makeExt(info)
-	if ext.Uri != info.request.Uri {
-		t.Errorf("The URI should be test.com. Got %s", ext.Uri)
-	}
-	if ext.RequestBody != string(info.request.Body) {
-		t.Errorf("The request body should be empty. Got %s", ext.RequestBody)
-	}
-	if ext.ResponseBody != "" {
-		t.Errorf("The response body should be empty. Got %s", ext.ResponseBody)
 	}
-	if ext.Status != 0 {
-		t.Errorf("The Status code should be 0. Got %d", ext.Status)
+
+	for _, test := range testCases {
+		result := makeExt(test.given)
+		assert.Equal(t, test.expected, result, test.description)
 	}
-	assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"header-1:value-1\"")
 }
 
-// TestSuccessfulResponseLogging makes sure that openrtb_ext works properly if the HTTP request is successful.
-func TestSuccessfulResponseLogging(t *testing.T) {
-	info := &httpCallInfo{
-		request: &adapters.RequestData{
-			Uri:  "test.com",
-			Body: []byte("request body"),
-			Headers: http.Header{
-				"header-1": []string{"value-1", "value-2"},
-			},
+func TestFilterHeader(t *testing.T) {
+	testCases := []struct {
+		description string
+		given       http.Header
+		expected    http.Header
+	}{
+		{
+			description: "Nil",
+			given:       nil,
+			expected:    nil,
 		},
-		response: &adapters.ResponseData{
-			StatusCode: 200,
-			Body:       []byte("response body"),
+		{
+			description: "Empty",
+			given:       http.Header{},
+			expected:    http.Header{},
+		},
+		{
+			description: "One",
+			given:       makeHeader(map[string][]string{"Key1": {"value1"}}),
+			expected:    makeHeader(map[string][]string{"Key1": {"value1"}}),
+		},
+		{
+			description: "Many",
+			given:       makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}),
+			expected:    makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}),
+		},
+		{
+			description: "Authorization Header Omitted",
+			given:       makeHeader(map[string][]string{"authorization": {"secret"}}),
+			expected:    http.Header{},
+		},
+		{
+			description: "Authorization Header Omitted - Case Insensitive",
+			given:       makeHeader(map[string][]string{"AuThOrIzAtIoN": {"secret"}}),
+			expected:    http.Header{},
+		},
+		{
+			description: "Authorization Header Omitted + Other Keys",
+			given:       makeHeader(map[string][]string{"authorization": {"secret"}, "Key1": {"value1"}}),
+			expected:    makeHeader(map[string][]string{"Key1": {"value1"}}),
 		},
 	}
-	ext := makeExt(info)
-	if ext.Uri != info.request.Uri {
-		t.Errorf("The URI should be test.com. Got %s", ext.Uri)
-	}
-	if ext.RequestBody != string(info.request.Body) {
-		t.Errorf("The request body should be \"request body\". Got %s", ext.RequestBody)
-	}
-	if ext.ResponseBody != string(info.response.Body) {
-		t.Errorf("The response body should be \"response body\". Got %s", ext.ResponseBody)
+
+	for _, test := range testCases {
+		result := filterHeader(test.given)
+		assert.Equal(t, test.expected, result, test.description)
 	}
-	if ext.Status != info.response.StatusCode {
-		t.Errorf("The Status code should be 0. Got %d", ext.Status)
+}
+
+func makeHeader(v map[string][]string) http.Header {
+	h := http.Header{}
+	for key, values := range v {
+		for _, value := range values {
+			h.Add(key, value)
+		}
 	}
-	assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"%s\". Got %s", info.request.Headers, ext.RequestHeaders)
+	return h
 }
 
 func TestMobileNativeTypes(t *testing.T) {
diff --git a/exchange/events_test.go b/exchange/events_test.go
index 6685918e..bde2b235 100644
--- a/exchange/events_test.go
+++ b/exchange/events_test.go
@@ -13,6 +13,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) {
 		enabledForAccount bool
 		enabledForRequest bool
 		bidType           openrtb_ext.BidType
+		generatedBidId    string
 	}
 	tests := []struct {
 		name string
@@ -21,7 +22,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) {
 	}{
 		{
 			name: "banner: events enabled for request, disabled for account",
-			args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner},
+			args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			want: &openrtb_ext.ExtBidPrebidEvents{
 				Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890",
 				Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890",
@@ -29,7 +30,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) {
 		},
 		{
 			name: "banner: events enabled for account, disabled for request",
-			args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner},
+			args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			want: &openrtb_ext.ExtBidPrebidEvents{
 				Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890",
 				Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890",
@@ -37,19 +38,27 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) {
 		},
 		{
 			name: "banner: events disabled for account and request",
-			args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner},
+			args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			want: nil,
 		},
 		{
 			name: "video: events enabled for account and request",
-			args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo},
+			args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""},
 			want: nil,
 		},
 		{
 			name: "video: events disabled for account and request",
-			args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo},
+			args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""},
 			want: nil,
 		},
+		{
+			name: "banner: use generated bid id",
+			args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: "randomId"},
+			want: &openrtb_ext.ExtBidPrebidEvents{
+				Win: "http://localhost/event?t=win&b=randomId&a=123456&bidder=openx&ts=1234567890",
+				Imp: "http://localhost/event?t=imp&b=randomId&a=123456&bidder=openx&ts=1234567890",
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -60,7 +69,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) {
 				auctionTimestampMs: 1234567890,
 				externalURL:        "http://localhost",
 			}
-			bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType}
+			bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId}
 			assert.Equal(t, tt.want, evData.makeBidExtEvents(bid, openrtb_ext.BidderOpenx))
 		})
 	}
@@ -71,6 +80,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) {
 		enabledForAccount bool
 		enabledForRequest bool
 		bidType           openrtb_ext.BidType
+		generatedBidId    string
 	}
 	tests := []struct {
 		name      string
@@ -80,40 +90,46 @@ func Test_eventsData_modifyBidJSON(t *testing.T) {
 	}{
 		{
 			name:      "banner: events enabled for request, disabled for account",
-			args:      args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner},
+			args:      args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			jsonBytes: []byte(`{"ID": "something"}`),
 			want:      []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`),
 		},
 		{
 			name:      "banner: events enabled for account, disabled for request",
-			args:      args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner},
+			args:      args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			jsonBytes: []byte(`{"ID": "something"}`),
 			want:      []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`),
 		},
 		{
 			name:      "banner: events disabled for account and request",
-			args:      args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner},
+			args:      args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			jsonBytes: []byte(`{"ID": "something"}`),
 			want:      []byte(`{"ID": "something"}`),
 		},
 		{
 			name:      "video: events disabled for account and request",
-			args:      args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo},
+			args:      args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""},
 			jsonBytes: []byte(`{"ID": "something"}`),
 			want:      []byte(`{"ID": "something"}`),
 		},
 		{
 			name:      "video: events enabled for account and request",
-			args:      args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo},
+			args:      args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""},
 			jsonBytes: []byte(`{"ID": "something"}`),
 			want:      []byte(`{"ID": "something"}`),
 		},
 		{
 			name:      "banner: broken json expected to fail patching",
-			args:      args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner},
+			args:      args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""},
 			jsonBytes: []byte(`broken json`),
 			want:      nil,
 		},
+		{
+			name:      "banner: generate bid id enabled",
+			args:      args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: "randomID"},
+			jsonBytes: []byte(`{"ID": "something"}`),
+			want:      []byte(`{"ID": "something", "wurl":"http://localhost/event?t=win&b=randomID&a=123456&bidder=openx&ts=1234567890"}`),
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -124,7 +140,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) {
 				auctionTimestampMs: 1234567890,
 				externalURL:        "http://localhost",
 			}
-			bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType}
+			bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId}
 			modifiedJSON, err := evData.modifyBidJSON(bid, openrtb_ext.BidderOpenx, tt.jsonBytes)
 			if tt.want != nil {
 				assert.NoError(t, err, "Unexpected error")
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 1cffa79b..beaa823d 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -652,6 +652,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 	e.gDPR = gdpr.AlwaysAllow{}
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
+	e.bidIDGenerator = &mockBidIDGenerator{false, false}
 
 	// Define mock incoming bid requeset
 	mockBidRequest := &openrtb2.BidRequest{
@@ -910,9 +911,10 @@ func TestBidReturnsCreative(t *testing.T) {
 	// Test set up
 	sampleBids := []*pbsOrtbBid{
 		{
-			bid:        sampleOpenrtbBid,
-			bidType:    openrtb_ext.BidTypeBanner,
-			bidTargets: map[string]string{},
+			bid:            sampleOpenrtbBid,
+			bidType:        openrtb_ext.BidTypeBanner,
+			bidTargets:     map[string]string{},
+			generatedBidID: "randomId",
 		},
 	}
 	sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}}
@@ -1625,8 +1627,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 			EEACountriesMap:     eeac,
 		},
 	}
-
-	ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig)
+	bidIdGenerator := &mockBidIDGenerator{}
+	if spec.BidIDGenerator != nil {
+		*bidIdGenerator = *spec.BidIDGenerator
+	}
+	ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator)
 	biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest)
 	debugLog := &DebugLog{}
 	if spec.DebugLog != nil {
@@ -1722,7 +1727,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons
 	}
 }
 
-func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy) Exchange {
+func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator) Exchange {
 	bidderAdapters := make(map[openrtb_ext.BidderName]adaptedBidder, len(expectations))
 	bidderInfos := make(config.BidderInfos, len(expectations))
 	for _, bidderName := range openrtb_ext.CoreBidderNames() {
@@ -1772,7 +1777,27 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		categoriesFetcher:   categoriesFetcher,
 		bidderInfo:          bidderInfos,
 		externalURL:         "http://localhost",
+		bidIDGenerator:      bidIDGenerator,
+	}
+}
+
+type mockBidIDGenerator struct {
+	GenerateBidID bool `json:"generateBidID"`
+	ReturnError   bool `json:"returnError"`
+}
+
+func (big *mockBidIDGenerator) Enabled() bool {
+	return big.GenerateBidID
+}
+
+func (big *mockBidIDGenerator) New() (string, error) {
+
+	if big.ReturnError {
+		err := errors.New("Test error generating bid.ext.prebid.bidid")
+		return "", err
 	}
+	return "mock_uuid", nil
+
 }
 
 func newExtRequest() openrtb_ext.ExtRequest {
@@ -1857,10 +1882,10 @@ func TestCategoryMapping(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -1912,10 +1937,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -1966,9 +1991,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -2048,9 +2073,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids := []*pbsOrtbBid{
 		&bid1_1,
@@ -2100,11 +2125,11 @@ func TestCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2179,11 +2204,11 @@ func TestNoCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2259,8 +2284,8 @@ func TestCategoryMappingBidderName(t *testing.T) {
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids1 := []*pbsOrtbBid{
 		&bid1_1,
@@ -2313,8 +2338,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBids1 := []*pbsOrtbBid{
 		&bid1_1,
@@ -2425,8 +2450,7 @@ func TestBidRejectionErrors(t *testing.T) {
 		innerBids := []*pbsOrtbBid{}
 		for _, bid := range test.bids {
 			currentBid := pbsOrtbBid{
-				bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false,
-			}
+				bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""}
 			innerBids = append(innerBids, &currentBid)
 		}
 
@@ -2474,8 +2498,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
 	bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
-	bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false}
+	bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
+	bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""}
 
 	innerBidsApn1 := []*pbsOrtbBid{
 		&bid1_Apn1,
@@ -2602,7 +2626,7 @@ func TestApplyDealSupport(t *testing.T) {
 			},
 		}
 
-		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false}
+		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""}
 		bidCategory := map[string]string{
 			bid.bid.ID: test.targ["hb_pb_cat_dur"],
 		}
@@ -2769,7 +2793,7 @@ func TestUpdateHbPbCatDur(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false}
+		bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""}
 		bidCategory := map[string]string{
 			bid.bid.ID: test.targ["hb_pb_cat_dur"],
 		}
@@ -2792,6 +2816,7 @@ type exchangeSpec struct {
 	DebugLog          *DebugLog              `json:"debuglog,omitempty"`
 	EventsEnabled     bool                   `json:"events_enabled,omitempty"`
 	StartTime         int64                  `json:"start_time_ms,omitempty"`
+	BidIDGenerator    *mockBidIDGenerator    `json:"bidIDGenerator,omitempty"`
 }
 
 type exchangeRequest struct {
diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json
new file mode 100644
index 00000000..17d3a3ec
--- /dev/null
+++ b/exchange/exchangetest/bid-id-invalid.json
@@ -0,0 +1,161 @@
+{
+  "bidIDGenerator": {
+    "generateBidID": true,
+    "returnError": true
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "appnexus": {
+              "placementId": 1
+            }
+          }
+        }
+      ],
+      "test": 1,
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includebrandcategory": {
+              "primaryadserver": 1,
+              "publisher": "",
+              "withcategory": true
+            },
+            "appendbiddernames": true
+          }
+        }
+      }
+    },
+    "usersyncs": {
+      "appnexus": "123"
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBid": {
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "apn-bid",
+                "impid": "my-imp-id",
+                "price": 0.3,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-1",
+                "cat": [
+                  "IAB1-1"
+                ]
+              },
+              "bidType": "video",
+              "bidVideo": {
+                "duration": 30,
+                "PrimaryCategory": ""
+              }
+            }
+          ]
+        }
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid",
+              "impid": "my-imp-id",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "prebid": {
+                  "type": "video",
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.20",
+                    "hb_pb_appnexus": "0.20",
+                    "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus",
+                    "hb_size": "200x250",
+                    "hb_size_appnexus": "200x250"
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "test": 1,
+          "ext": {
+            "prebid": {
+              "targeting": {
+                "includebrandcategory": {
+                  "primaryadserver": 1,
+                  "publisher": "",
+                  "withcategory": true
+                },
+                "appendbiddernames": true
+              }
+            }
+          }
+        }
+      },
+      "errors": {
+        "prebid": [
+          {
+            "code": 999,
+            "message": "Error generating bid.ext.prebid.bidid"
+          }
+        ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json
new file mode 100644
index 00000000..8da6410e
--- /dev/null
+++ b/exchange/exchangetest/bid-id-valid.json
@@ -0,0 +1,154 @@
+{
+  "bidIDGenerator": {
+    "generateBidID": true,
+    "returnError": false
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "appnexus": {
+              "placementId": 1
+            }
+          }
+        }
+      ],
+      "test": 1,
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includebrandcategory": {
+              "primaryadserver": 1,
+              "publisher": "",
+              "withcategory": true
+            },
+            "appendbiddernames": true
+          }
+        }
+      }
+    },
+    "usersyncs": {
+      "appnexus": "123"
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBid": {
+          "pbsBids": [
+            {
+              "ortbBid": {
+                "id": "apn-bid",
+                "impid": "my-imp-id",
+                "price": 0.3,
+                "w": 200,
+                "h": 250,
+                "crid": "creative-1",
+                "cat": [
+                  "IAB1-1"
+                ]
+              },
+              "bidType": "video",
+              "bidVideo": {
+                "duration": 30,
+                "PrimaryCategory": ""
+              }
+            }
+          ]
+        }
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid",
+              "impid": "my-imp-id",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "prebid": {
+                  "bidid": "mock_uuid",
+                  "type": "video",
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.20",
+                    "hb_pb_appnexus": "0.20",
+                    "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus",
+                    "hb_size": "200x250",
+                    "hb_size_appnexus": "200x250"
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "test": 1,
+          "ext": {
+            "prebid": {
+              "targeting": {
+                "includebrandcategory": {
+                  "primaryadserver": 1,
+                  "publisher": "",
+                  "withcategory": true
+                },
+                "appendbiddernames": true
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json
index 6e29ccdf..13afb840 100644
--- a/exchange/exchangetest/events-vast-account-off-request-on.json
+++ b/exchange/exchangetest/events-vast-account-off-request-on.json
@@ -1,6 +1,10 @@
 {
   "events_enabled": false,
   "start_time_ms": 1234567890,
+  "bidIDGenerator": {
+    "generateBidID": true,
+    "returnError": false
+  },
   "incomingRequest": {
     "ortbRequest": {
       "id": "some-request-id",
@@ -105,6 +109,7 @@
               "crid": "creative-4",
               "ext": {
                 "prebid": {
+                  "bidid": "mock_uuid",
                   "type": "video"
                 }
               }
@@ -116,7 +121,7 @@
           "bid": [
             {
               "id": "winning-bid",
-              "adm": "<VAST version=\"3.0\"><Ad><Wrapper><AdSystem>prebid.org wrapper</AdSystem><VASTAdTagURI><![CDATA[http://domain.com/winning-bid]]></VASTAdTagURI><Impression><![CDATA[http://localhost/event?t=imp&b=winning-bid&a=testaccount&bidder=appnexus&f=b&ts=1234567890]]></Impression><Creatives></Creatives></Wrapper></Ad></VAST>",
+              "adm": "<VAST version=\"3.0\"><Ad><Wrapper><AdSystem>prebid.org wrapper</AdSystem><VASTAdTagURI><![CDATA[http://domain.com/winning-bid]]></VASTAdTagURI><Impression><![CDATA[http://localhost/event?t=imp&b=mock_uuid&a=testaccount&bidder=appnexus&f=b&ts=1234567890]]></Impression><Creatives></Creatives></Wrapper></Ad></VAST>",
               "nurl": "http://domain.com/winning-bid",
               "impid": "my-imp-id",
               "price": 0.71,
@@ -125,6 +130,7 @@
               "crid": "creative-1",
               "ext": {
                 "prebid": {
+                  "bidid": "mock_uuid",
                   "type": "video",
                   "targeting": {
                       "hb_bidder": "appnexus",
@@ -139,7 +145,7 @@
             },
             {
               "id": "losing-bid",
-              "adm": "<VAST version=\"3.0\"><Ad><Wrapper><AdSystem>prebid.org wrapper</AdSystem><VASTAdTagURI><![CDATA[http://domain.com/losing-bid]]></VASTAdTagURI><Impression><![CDATA[http://localhost/event?t=imp&b=losing-bid&a=testaccount&bidder=appnexus&f=b&ts=1234567890]]></Impression><Creatives></Creatives></Wrapper></Ad></VAST>",
+              "adm": "<VAST version=\"3.0\"><Ad><Wrapper><AdSystem>prebid.org wrapper</AdSystem><VASTAdTagURI><![CDATA[http://domain.com/losing-bid]]></VASTAdTagURI><Impression><![CDATA[http://localhost/event?t=imp&b=mock_uuid&a=testaccount&bidder=appnexus&f=b&ts=1234567890]]></Impression><Creatives></Creatives></Wrapper></Ad></VAST>",
               "nurl": "http://domain.com/losing-bid",
               "impid": "my-imp-id",
               "price": 0.21,
@@ -148,6 +154,7 @@
               "crid": "creative-2",
               "ext": {
                 "prebid": {
+                  "bidid": "mock_uuid",
                   "type": "video"
                 }
               }
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index 2cdf4034..add3b19e 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -95,6 +95,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		currencyConverter:   currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		UsersyncIfAmbiguous: false,
 		categoriesFetcher:   categoriesFetcher,
+		bidIDGenerator:      &mockBidIDGenerator{false, false},
 	}
 
 	imps := buildImps(t, mockBids)
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 5b2e8046..a389a007 100755
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -41,6 +41,7 @@ func TestNewSyncerMap(t *testing.T) {
 			string(openrtb_ext.BidderConsumable):       syncConfig,
 			string(openrtb_ext.BidderConversant):       syncConfig,
 			string(openrtb_ext.BidderCpmstar):          syncConfig,
+			string(openrtb_ext.BidderCriteo):           syncConfig,
 			string(openrtb_ext.BidderDatablocks):       syncConfig,
 			string(openrtb_ext.BidderDmx):              syncConfig,
 			string(openrtb_ext.BidderDeepintent):       syncConfig,
@@ -106,6 +107,7 @@ func TestNewSyncerMap(t *testing.T) {
 		openrtb_ext.BidderAdot:         true,
 		openrtb_ext.BidderAdprime:      true,
 		openrtb_ext.BidderApplogy:      true,
+		openrtb_ext.BidderBidmachine:   true,
 		openrtb_ext.BidderEpom:         true,
 		openrtb_ext.BidderDecenterAds:  true,
 		openrtb_ext.BidderInMobi:       true,