Repository: prebid/prebid-server
Pull request #2425 (2022-10-17 10:13:48)

Smaato: Add Native
Add native to Smaato Adapter


Patch:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 210dbdb5..f4ea099d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -6,7 +6,7 @@
 		"dockerfile": "Dockerfile",
 		"args": {
 			// Update the VARIANT arg to pick a version of Go
-			"VARIANT": "1.16",
+			"VARIANT": "1.19",
 			// Options
 			"INSTALL_NODE": "false",
 			"NODE_VERSION": "lts/*"
@@ -14,16 +14,21 @@
 	},
 	"containerEnv": {
 		"GOPRIVATE": "${localEnv:GOPRIVATE}",
+		"GOFLAGS": "-buildvcs=false",
 		"PBS_GDPR_DEFAULT_VALUE": "0"
 	},
 	"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
 
 	// Set *default* container specific settings.json values on container create.
 	"settings": {
-		"terminal.integrated.shell.linux": "/bin/bash",
-		"go.useGoProxyToCheckForToolUpdates": false,
+		"terminal.integrated.profiles.linux": {
+			"bash": {
+				"path": "/usr/bin/bash"
+			}
+		},
+		"terminal.integrated.defaultProfile.linux": "bash",
+		"go.toolsManagement.checkForUpdates": "off",
 		"go.gopath": "/go",
-		//"go.toolsGopath": "/tmp/go",
 	},
 
 	// Add the IDs of extensions you want installed when the container is created.
diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml
index 63dd7a97..5cf8cdb7 100644
--- a/.github/workflows/validate-merge.yml
+++ b/.github/workflows/validate-merge.yml
@@ -12,7 +12,7 @@ jobs:
     - name: Install Go
       uses: actions/setup-go@v2
       with:
-        go-version: 1.16.4
+        go-version: 1.19.2
 
     - name: Checkout Merged Branch
       uses: actions/checkout@v2
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 972d6c0e..7d1fddc2 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -10,7 +10,7 @@ jobs:
   validate:
     strategy:
       matrix:
-        go-version: [1.16.x, 1.17.x]
+        go-version: [1.18.x, 1.19.x]
         os: [ubuntu-20.04]
     runs-on: ${{ matrix.os }}
     
diff --git a/Dockerfile b/Dockerfile
index c2214da5..80269c90 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,8 +3,8 @@ RUN apt-get update && \
     apt-get -y upgrade && \
     apt-get install -y wget
 WORKDIR /tmp
-RUN wget https://dl.google.com/go/go1.16.4.linux-amd64.tar.gz && \
-    tar -xf go1.16.4.linux-amd64.tar.gz && \
+RUN wget https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz && \
+    tar -xf go1.19.2.linux-amd64.tar.gz && \
     mv go /usr/local
 RUN mkdir -p /app/prebid-server/
 WORKDIR /app/prebid-server/
diff --git a/README.md b/README.md
index dab5523e..e88a4b26 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Please consider [registering your Prebid Server](https://docs.prebid.org/prebid-
 
 ## Installation
 
-First install [Go](https://golang.org/doc/install) version 1.16 or newer.
+First install [Go](https://golang.org/doc/install) version 1.18 or newer.
 
 Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules).
 We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`.
diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go
index 63e2e476..50720d25 100644
--- a/adapters/adnuntius/adnuntius.go
+++ b/adapters/adnuntius/adnuntius.go
@@ -18,8 +18,9 @@ import (
 
 type QueryString map[string]string
 type adapter struct {
-	time     timeutil.Time
-	endpoint string
+	time      timeutil.Time
+	endpoint  string
+	extraInfo string
 }
 type adnAdunit struct {
 	AuId       string    `json:"auId"`
@@ -97,6 +98,7 @@ func setHeaders(ortbRequest openrtb2.BidRequest) http.Header {
 
 func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool) (string, []error) {
 	uri, err := url.Parse(a.endpoint)
+	endpointUrl := a.endpoint
 	if err != nil {
 		return "", []error{fmt.Errorf("failed to parse Adnuntius endpoint: %v", err)}
 	}
@@ -124,8 +126,12 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool
 	tzo := -offset / minutesInHour
 
 	q := uri.Query()
-	if gdpr != "" && consent != "" {
+	if gdpr != "" {
+		endpointUrl = a.extraInfo
 		q.Set("gdpr", gdpr)
+	}
+
+	if consent != "" {
 		q.Set("consentString", consent)
 	}
 
@@ -136,7 +142,7 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool
 	q.Set("tzo", fmt.Sprint(tzo))
 	q.Set("format", "json")
 
-	url := a.endpoint + "?" + q.Encode()
+	url := endpointUrl + "?" + q.Encode()
 	return url, nil
 }
 
@@ -281,6 +287,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapte
 }
 
 func getGDPR(request *openrtb2.BidRequest) (string, string, error) {
+
 	gdpr := ""
 	var extRegs openrtb_ext.ExtRegs
 	if request.Regs != nil && request.Regs.Ext != nil {
diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go
index 476c5a99..66f331bf 100644
--- a/adapters/aja/aja.go
+++ b/adapters/aja/aja.go
@@ -124,6 +124,9 @@ func (a *AJAAdapter) MakeBids(bidReq *openrtb2.BidRequest, adapterReq *adapters.
 			}
 		}
 	}
+	if bidResp.Cur != "" {
+		bidderResp.Currency = bidResp.Cur
+	}
 	return bidderResp, errors
 }
 
diff --git a/adapters/andbeyondmedia/andbeyondmedia.go b/adapters/beyondmedia/beyondmedia.go
similarity index 89%
rename from adapters/andbeyondmedia/andbeyondmedia.go
rename to adapters/beyondmedia/beyondmedia.go
index c7249020..dd56d849 100644
--- a/adapters/andbeyondmedia/andbeyondmedia.go
+++ b/adapters/beyondmedia/beyondmedia.go
@@ -1,4 +1,4 @@
-package andbeyondmedia
+package beyondmedia
 
 import (
 	"encoding/json"
@@ -17,7 +17,7 @@ type adapter struct {
 }
 
 type reqBodyExt struct {
-	AndBeyondMediaBidderExt reqBodyExtBidder `json:"bidder"`
+	BeyondMediaBidderExt reqBodyExtBidder `json:"bidder"`
 }
 
 type reqBodyExtBidder struct {
@@ -41,18 +41,18 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
 		reqCopy.Imp = []openrtb2.Imp{imp}
 
 		var bidderExt adapters.ExtImpBidder
-		var andBeyondMediaExt openrtb_ext.ImpExtAndBeyondMedia
+		var beyondMediaExt openrtb_ext.ImpExtBeyondMedia
 
 		if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil {
 			return nil, []error{err}
 		}
-		if err = json.Unmarshal(bidderExt.Bidder, &andBeyondMediaExt); err != nil {
+		if err = json.Unmarshal(bidderExt.Bidder, &beyondMediaExt); err != nil {
 			return nil, []error{err}
 		}
 
-		temp := reqBodyExt{AndBeyondMediaBidderExt: reqBodyExtBidder{}}
-		temp.AndBeyondMediaBidderExt.PlacementID = andBeyondMediaExt.PlacementID
-		temp.AndBeyondMediaBidderExt.Type = "publisher"
+		temp := reqBodyExt{BeyondMediaBidderExt: reqBodyExtBidder{}}
+		temp.BeyondMediaBidderExt.PlacementID = beyondMediaExt.PlacementID
+		temp.BeyondMediaBidderExt.Type = "publisher"
 
 		finalyImpExt, err := json.Marshal(temp)
 		if err != nil {
diff --git a/adapters/bidstack/bidstack.go b/adapters/bidstack/bidstack.go
new file mode 100644
index 00000000..55ce400a
--- /dev/null
+++ b/adapters/bidstack/bidstack.go
@@ -0,0 +1,126 @@
+package bidstack
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+const (
+	currencyUSDISO4217 = "USD"
+
+	headerKeyAuthorization         = "Authorization"
+	headerValueAuthorizationBearer = "Bearer "
+	headerKeyContentType           = "Content-Type"
+
+	contentTypeApplicationJSON = "application/json"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+// Builder builds a new instance of the Bidstack adapter for the given bidder with the given config.
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+
+	return bidder, nil
+}
+
+func (a adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	headers, err := prepareHeaders(request)
+	if err != nil {
+		return nil, []error{fmt.Errorf("headers prepare: %v", err)}
+	}
+
+	for i := range request.Imp {
+		imp := &request.Imp[i]
+		if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != currencyUSDISO4217 {
+			convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, currencyUSDISO4217)
+			if err != nil {
+				return nil, []error{err}
+			}
+			imp.BidFloorCur = currencyUSDISO4217
+			imp.BidFloor = convertedValue
+		}
+	}
+
+	requestJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{fmt.Errorf("bid request marshal: %v", err)}
+	}
+
+	requestData := &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Headers: headers,
+		Body:    requestJSON,
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	switch responseData.StatusCode {
+	case http.StatusNoContent:
+		return nil, nil
+	case http.StatusBadRequest:
+		return nil, []error{errors.New("bad request from publisher")}
+	case http.StatusOK:
+		break
+	default:
+		return nil, []error{fmt.Errorf("unexpected response status code: %v", responseData.StatusCode)}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{fmt.Errorf("bid response unmarshal: %v", err)}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: openrtb_ext.BidTypeVideo,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+
+	return bidResponse, nil
+}
+
+func prepareHeaders(request *openrtb2.BidRequest) (headers http.Header, err error) {
+	bidderImpExt, err := getBidderExt(request.Imp[0])
+	if err != nil {
+		return nil, fmt.Errorf("get bidder ext: %v", err)
+	}
+
+	return http.Header{
+		headerKeyContentType:   {contentTypeApplicationJSON},
+		headerKeyAuthorization: {headerValueAuthorizationBearer + bidderImpExt.PublisherID},
+	}, nil
+}
+
+func getBidderExt(imp openrtb2.Imp) (bidderImpExt openrtb_ext.ImpExtBidstack, err error) {
+	var impExt adapters.ExtImpBidder
+	if err = json.Unmarshal(imp.Ext, &impExt); err != nil {
+		return bidderImpExt, fmt.Errorf("imp ext: %v", err)
+	}
+	if err = json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil {
+		return bidderImpExt, fmt.Errorf("bidder ext: %v", err)
+	}
+	return bidderImpExt, nil
+}
diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go
index 48008fc1..dc9759c2 100644
--- a/adapters/consumable/consumable.go
+++ b/adapters/consumable/consumable.go
@@ -39,6 +39,7 @@ type bidRequest struct {
 	GDPR               *bidGdpr             `json:"gdpr,omitempty"`
 	Coppa              bool                 `json:"coppa,omitempty"`
 	SChain             openrtb2.SupplyChain `json:"schain"`
+	Content            *openrtb2.Content    `json:"content,omitempty"`
 }
 
 type placement struct {
@@ -189,6 +190,12 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *
 
 	body.Coppa = request.Regs != nil && request.Regs.COPPA > 0
 
+	if request.Site != nil && request.Site.Content != nil {
+		body.Content = request.Site.Content
+	} else if request.App != nil && request.App.Content != nil {
+		body.Content = request.App.Content
+	}
+
 	for i, impression := range request.Imp {
 
 		_, consumableExt, err := extractExtensions(impression)
diff --git a/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json b/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json
new file mode 100644
index 00000000..56d24166
--- /dev/null
+++ b/adapters/consumable/consumable/supplemental/simple-banner-content-meta.json
@@ -0,0 +1,128 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{ "w": 728, "h": 250 }]
+        },
+        "ext": {
+          "bidder": {
+            "networkId": 11,
+            "siteId": 32,
+            "unitId": 42
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+      "ip": "123.123.123.123"
+    },
+    "site": {
+      "domain": "www.some.com",
+      "page": "http://www.some.com/page-where-ad-will-be-shown",
+      "content": {
+        "id": "1009680902",
+        "title": "What You Know Bout Love",
+        "context": 3,
+        "url": "https://www.deezer.com/track/1009680902",
+        "artist": "Pop Smoke",
+        "album": "Shoot For The Stars Aim For The Moon"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://e.serverbid.com/api/v2",
+        "headers": {
+          "Accept": ["application/json"],
+          "Content-Type": ["application/json"],
+          "User-Agent": [
+            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+          ],
+          "X-Forwarded-For": ["123.123.123.123"],
+          "Forwarded": ["for=123.123.123.123"],
+          "Origin": ["http://www.some.com"],
+          "Referer": ["http://www.some.com/page-where-ad-will-be-shown"]
+        },
+        "body": {
+          "content": {
+            "id": "1009680902",
+            "title": "What You Know Bout Love",
+            "context": 3,
+            "url": "https://www.deezer.com/track/1009680902",
+            "artist": "Pop Smoke",
+            "album": "Shoot For The Stars Aim For The Moon"
+          },
+          "placements": [
+            {
+              "adTypes": [2730],
+              "divName": "test-imp-id",
+              "networkId": 11,
+              "siteId": 32,
+              "unitId": 42
+            }
+          ],
+          "schain": {
+            "complete": 0,
+            "nodes": null,
+            "ver": ""
+          },
+          "networkId": 11,
+          "siteId": 32,
+          "unitId": 42,
+          "time": 1451651415,
+          "url": "http://www.some.com/page-where-ad-will-be-shown",
+          "includePricingData": true,
+          "user": {},
+          "enableBotFiltering": true,
+          "parallel": true
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "decisions": {
+            "test-imp-id": {
+              "adId": 1234567890,
+              "pricing": {
+                "clearPrice": 0.5
+              },
+              "width": 728,
+              "height": 250,
+              "impressionUrl": "http://localhost:8080/shown",
+              "contents": [
+                {
+                  "body": "<blink>Remember this: https://www.google.com/search?q=blink+tag ?</blink>"
+                }
+              ]
+            }
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-request-id",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "<blink>Remember this: https://www.google.com/search?q=blink+tag ?</blink>",
+            "crid": "1234567890",
+            "exp": 30,
+            "w": 728,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go
index 7f37386c..17439ad8 100644
--- a/adapters/logicad/logicad.go
+++ b/adapters/logicad/logicad.go
@@ -146,6 +146,9 @@ func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb2.BidRequest, ex
 			BidType: openrtb_ext.BidTypeBanner,
 		})
 	}
+	if bidResp.Cur != "" {
+		bidResponse.Currency = bidResp.Cur
+	}
 	return bidResponse, nil
 }
 
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 900ec68d..f00d0d24 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -470,6 +470,9 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 			bidResponse.Bids = append(bidResponse.Bids, typedBid)
 		}
 	}
+	if bidResp.Cur != "" {
+		bidResponse.Currency = bidResp.Cur
+	}
 	return bidResponse, errs
 }
 
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 28879475..7bf80c75 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -12,6 +12,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/prebid/prebid-server/util/maputil"
 
 	"github.com/buger/jsonparser"
 	"github.com/prebid/openrtb/v17/adcom1"
@@ -20,6 +21,8 @@ import (
 
 const badvLimitSize = 50
 
+var bannerExtContent = []byte(`{"rp":{"mime":"text/html"}}`)
+
 type RubiconAdapter struct {
 	URI          string
 	XAPIUsername string
@@ -88,22 +91,16 @@ type rubiconUserExtRP struct {
 	Target json.RawMessage `json:"target,omitempty"`
 }
 
-type rubiconExtUserTpID struct {
-	Source string `json:"source"`
-	UID    string `json:"uid"`
-}
-
 type rubiconDataExt struct {
 	SegTax int `json:"segtax"`
 }
 
 type rubiconUserExt struct {
-	Consent     string               `json:"consent,omitempty"`
-	Eids        []openrtb2.EID       `json:"eids,omitempty"`
-	TpID        []rubiconExtUserTpID `json:"tpid,omitempty"`
-	RP          rubiconUserExtRP     `json:"rp"`
-	LiverampIdl string               `json:"liveramp_idl,omitempty"`
-	Data        json.RawMessage      `json:"data,omitempty"`
+	Consent     string           `json:"consent,omitempty"`
+	Eids        []openrtb2.EID   `json:"eids,omitempty"`
+	RP          rubiconUserExtRP `json:"rp"`
+	LiverampIdl string           `json:"liveramp_idl,omitempty"`
+	Data        json.RawMessage  `json:"data,omitempty"`
 }
 
 type rubiconSiteExtRP struct {
@@ -124,9 +121,7 @@ type rubiconPubExt struct {
 }
 
 type rubiconBannerExtRP struct {
-	SizeID     int    `json:"size_id,omitempty"`
-	AltSizeIDs []int  `json:"alt_size_ids,omitempty"`
-	MIME       string `json:"mime"`
+	MIME string `json:"mime"`
 }
 
 type rubiconBannerExt struct {
@@ -160,7 +155,13 @@ type rubiconBidResponse struct {
 
 type rubiconSeatBid struct {
 	openrtb2.SeatBid
-	Buyer string `json:"buyer,omitempty"`
+	Buyer string       `json:"buyer,omitempty"`
+	Bid   []rubiconBid `json:"bid"`
+}
+
+type rubiconBid struct {
+	openrtb2.Bid
+	AdmObject json.RawMessage `json:"admobject,omitempty"`
 }
 
 type extPrebid struct {
@@ -173,94 +174,6 @@ type rubiSize struct {
 	h uint16
 }
 
-var rubiSizeMap = map[rubiSize]int{
-	{w: 468, h: 60}:    1,
-	{w: 728, h: 90}:    2,
-	{w: 728, h: 91}:    2,
-	{w: 120, h: 90}:    5,
-	{w: 125, h: 125}:   7,
-	{w: 120, h: 600}:   8,
-	{w: 160, h: 600}:   9,
-	{w: 300, h: 600}:   10,
-	{w: 200, h: 200}:   13,
-	{w: 250, h: 250}:   14,
-	{w: 300, h: 250}:   15,
-	{w: 300, h: 251}:   15,
-	{w: 336, h: 280}:   16,
-	{w: 240, h: 400}:   17,
-	{w: 300, h: 100}:   19,
-	{w: 980, h: 120}:   31,
-	{w: 250, h: 360}:   32,
-	{w: 180, h: 500}:   33,
-	{w: 980, h: 150}:   35,
-	{w: 468, h: 400}:   37,
-	{w: 930, h: 180}:   38,
-	{w: 750, h: 100}:   39,
-	{w: 750, h: 200}:   40,
-	{w: 750, h: 300}:   41,
-	{w: 320, h: 50}:    43,
-	{w: 300, h: 50}:    44,
-	{w: 300, h: 300}:   48,
-	{w: 1024, h: 768}:  53,
-	{w: 300, h: 1050}:  54,
-	{w: 970, h: 90}:    55,
-	{w: 970, h: 250}:   57,
-	{w: 1000, h: 90}:   58,
-	{w: 320, h: 80}:    59,
-	{w: 320, h: 150}:   60,
-	{w: 1000, h: 1000}: 61,
-	{w: 580, h: 500}:   64,
-	{w: 640, h: 480}:   65,
-	{w: 930, h: 600}:   66,
-	{w: 320, h: 480}:   67,
-	{w: 1800, h: 1000}: 68,
-	{w: 320, h: 320}:   72,
-	{w: 320, h: 160}:   73,
-	{w: 980, h: 240}:   78,
-	{w: 980, h: 300}:   79,
-	{w: 980, h: 400}:   80,
-	{w: 480, h: 300}:   83,
-	{w: 300, h: 120}:   85,
-	{w: 548, h: 150}:   90,
-	{w: 970, h: 310}:   94,
-	{w: 970, h: 100}:   95,
-	{w: 970, h: 210}:   96,
-	{w: 480, h: 320}:   101,
-	{w: 768, h: 1024}:  102,
-	{w: 480, h: 280}:   103,
-	{w: 250, h: 800}:   105,
-	{w: 320, h: 240}:   108,
-	{w: 1000, h: 300}:  113,
-	{w: 320, h: 100}:   117,
-	{w: 800, h: 250}:   125,
-	{w: 200, h: 600}:   126,
-	{w: 980, h: 600}:   144,
-	{w: 980, h: 150}:   145,
-	{w: 1000, h: 250}:  152,
-	{w: 640, h: 320}:   156,
-	{w: 320, h: 250}:   159,
-	{w: 250, h: 600}:   179,
-	{w: 600, h: 300}:   195,
-	{w: 640, h: 360}:   198,
-	{w: 640, h: 200}:   199,
-	{w: 1030, h: 590}:  213,
-	{w: 980, h: 360}:   214,
-	{w: 320, h: 180}:   229,
-	{w: 2000, h: 1400}: 230,
-	{w: 580, h: 400}:   232,
-	{w: 480, h: 820}:   256,
-	{w: 400, h: 600}:   257,
-	{w: 500, h: 200}:   258,
-	{w: 998, h: 200}:   259,
-	{w: 970, h: 1000}:  264,
-	{w: 1920, h: 1080}: 265,
-	{w: 1800, h: 200}:  274,
-	{w: 320, h: 500}:   278,
-	{w: 320, h: 400}:   282,
-	{w: 640, h: 380}:   288,
-	{w: 500, h: 1000}:  548,
-}
-
 // defines the contract for bidrequest.user.ext.eids[i].ext
 type rubiconUserExtEidExt struct {
 	Segments []string `json:"segments,omitempty"`
@@ -273,61 +186,10 @@ type rubiconUserExtEidUidExt struct {
 }
 
 type mappedRubiconUidsParam struct {
-	tpIds       []rubiconExtUserTpID
 	segments    []string
 	liverampIdl string
 }
 
-// MAS algorithm
-func findPrimary(alt []int) (int, []int) {
-	min, pos, primary := 0, 0, 0
-	for i, size := range alt {
-		if size == 15 {
-			primary = 15
-			pos = i
-			break
-		} else if size == 2 {
-			primary = 2
-			pos = i
-		} else if size == 9 && primary != 2 {
-			primary = 9
-			pos = i
-		} else if size < alt[min] {
-			min = i
-		}
-	}
-	if primary == 0 {
-		primary = alt[min]
-		pos = min
-	}
-
-	alt = append(alt[:pos], alt[pos+1:]...)
-	return primary, alt
-}
-
-func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err error) {
-	// Fixes #317
-	if len(sizes) < 1 {
-		err = &errortypes.BadInput{
-			Message: "rubicon imps must have at least one imp.format element",
-		}
-		return
-	}
-	for _, size := range sizes {
-		if rs, ok := rubiSizeMap[rubiSize{w: uint16(size.W), h: uint16(size.H)}]; ok {
-			alt = append(alt, rs)
-		}
-	}
-	if len(alt) > 0 {
-		primary, alt = findPrimary(alt)
-	} else {
-		err = &errortypes.BadInput{
-			Message: "No primary size found",
-		}
-	}
-	return
-}
-
 func resolveVideoSizeId(placement adcom1.VideoPlacementSubtype, instl int8, impId string) (sizeID int, err error) {
 	if placement != 0 {
 		if placement == 1 {
@@ -479,13 +341,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 						userBuyerUID = extractUserBuyerUID(userExt.Eids)
 					}
 
-					mappedRubiconUidsParam, errors := getTpIdsAndSegments(userExt.Eids)
+					mappedRubiconUidsParam, errors := getSegments(userExt.Eids)
 					if len(errors) > 0 {
 						errs = append(errs, errors...)
 						continue
 					}
 
-					if err := updateUserExtWithTpIdsAndSegments(&userExtRP, mappedRubiconUidsParam); err != nil {
+					if err := updateUserExtWithSegments(&userExtRP, mappedRubiconUidsParam); err != nil {
 						errs = append(errs, err)
 						continue
 					}
@@ -515,6 +377,8 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 		}
 
 		isVideo := isVideo(imp)
+		impType := openrtb_ext.BidTypeVideo
+		requestNative := make(map[string]interface{})
 		if isVideo {
 			videoCopy := *imp.Video
 
@@ -537,21 +401,33 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 			videoCopy.Ext, err = json.Marshal(&videoExt)
 			imp.Video = &videoCopy
 			imp.Banner = nil
-		} else {
-			primarySizeID, altSizeIDs, err := parseRubiconSizes(imp.Banner.Format)
+			imp.Native = nil
+		} else if imp.Banner != nil {
+			bannerCopy := *imp.Banner
+			if len(bannerCopy.Format) < 1 && (bannerCopy.W == nil || *bannerCopy.W == 0 && bannerCopy.H == nil || *bannerCopy.H == 0) {
+				errs = append(errs, &errortypes.BadInput{
+					Message: "rubicon imps must have at least one imp.format element",
+				})
+				continue
+			}
+			bannerCopy.Ext = bannerExtContent
 			if err != nil {
 				errs = append(errs, err)
 				continue
 			}
-			bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}}
-			bannerCopy := *imp.Banner
-			bannerCopy.Ext, err = json.Marshal(&bannerExt)
+			imp.Banner = &bannerCopy
+			imp.Video = nil
+			imp.Native = nil
+			impType = openrtb_ext.BidTypeBanner
+		} else {
+			native, err := resolveNativeObject(imp.Native, requestNative)
 			if err != nil {
 				errs = append(errs, err)
 				continue
 			}
-			imp.Banner = &bannerCopy
+			imp.Native = native
 			imp.Video = nil
+			impType = openrtb_ext.BidTypeNative
 		}
 
 		accountId, err := rubiconExt.AccountId.Int64()
@@ -607,6 +483,10 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 		rubiconRequest.Ext = nil
 
 		reqJSON, err := json.Marshal(rubiconRequest)
+		if impType == openrtb_ext.BidTypeNative && len(requestNative) > 0 {
+			reqJSON, err = setImpNative(reqJSON, requestNative)
+		}
+
 		if err != nil {
 			errs = append(errs, err)
 			continue
@@ -921,42 +801,17 @@ func extractUserBuyerUID(eids []openrtb2.EID) string {
 	return ""
 }
 
-func getTpIdsAndSegments(eids []openrtb2.EID) (mappedRubiconUidsParam, []error) {
+func getSegments(eids []openrtb2.EID) (mappedRubiconUidsParam, []error) {
 	rubiconUidsParam := mappedRubiconUidsParam{
-		tpIds:    make([]rubiconExtUserTpID, 0),
 		segments: make([]string, 0),
 	}
 	errs := make([]error, 0)
 
 	for _, eid := range eids {
 		switch eid.Source {
-		case "adserver.org":
-			uids := eid.UIDs
-			if len(uids) > 0 {
-				uid := uids[0]
-
-				if uid.Ext != nil {
-					var eidUidExt rubiconUserExtEidUidExt
-					if err := json.Unmarshal(uid.Ext, &eidUidExt); err != nil {
-						errs = append(errs, &errortypes.BadInput{
-							Message: err.Error(),
-						})
-						continue
-					}
-
-					if eidUidExt.RtiPartner == "TDID" {
-						rubiconUidsParam.tpIds = append(rubiconUidsParam.tpIds, rubiconExtUserTpID{Source: "tdid", UID: uid.ID})
-					}
-				}
-			}
 		case "liveintent.com":
 			uids := eid.UIDs
 			if len(uids) > 0 {
-				uidId := uids[0].ID
-				if uidId != "" {
-					rubiconUidsParam.tpIds = append(rubiconUidsParam.tpIds, rubiconExtUserTpID{Source: "liveintent.com", UID: uidId})
-				}
-
 				if eid.Ext != nil {
 					var eidExt rubiconUserExtEidExt
 					if err := json.Unmarshal(eid.Ext, &eidExt); err != nil {
@@ -982,9 +837,8 @@ func getTpIdsAndSegments(eids []openrtb2.EID) (mappedRubiconUidsParam, []error)
 	return rubiconUidsParam, errs
 }
 
-func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error {
-	if len(rubiconUidsParam.tpIds) > 0 {
-		userExtRP.TpID = rubiconUidsParam.tpIds
+func updateUserExtWithSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error {
+	if len(rubiconUidsParam.segments) > 0 {
 
 		if rubiconUidsParam.segments != nil {
 			userExtRPTarget := make(map[string]interface{})
@@ -1021,6 +875,64 @@ func isFullyPopulatedVideo(video *openrtb2.Video) bool {
 	return video.MIMEs != nil && video.Protocols != nil && video.MaxDuration != 0 && video.Linearity != 0 && video.API != nil
 }
 
+func resolveNativeObject(native *openrtb2.Native, target map[string]interface{}) (*openrtb2.Native, error) {
+	if native == nil {
+		return nil, fmt.Errorf("Native object is not present for request")
+	}
+	ver := native.Ver
+	if ver == "1.0" || ver == "1.1" {
+		return native, nil
+	}
+
+	err := json.Unmarshal([]byte(native.Request), &target)
+	if err != nil {
+		return nil, err
+	}
+
+	if _, ok := target["eventtrackers"].([]interface{}); !ok {
+		return nil, fmt.Errorf("Eventtrackers are not present or not of array type")
+	}
+
+	if _, ok := target["context"].(float64); !ok {
+		return nil, fmt.Errorf("Context is not present or not of int type")
+	}
+
+	if _, ok := target["plcmttype"].(float64); !ok {
+		return nil, fmt.Errorf("Plcmttype is not present or not of int type")
+	}
+
+	return native, nil
+}
+
+func setImpNative(jsonData []byte, requestNative map[string]interface{}) ([]byte, error) {
+	var jsonMap map[string]interface{}
+	if err := json.Unmarshal(jsonData, &jsonMap); err != nil {
+		return jsonData, err
+	}
+
+	var impMap map[string]interface{}
+	if impSlice, ok := maputil.ReadEmbeddedSlice(jsonMap, "imp"); !ok {
+		return jsonData, fmt.Errorf("unable to find imp in json data")
+	} else if len(impSlice) == 0 {
+		return jsonData, fmt.Errorf("unable to find imp[0] in json data")
+	} else if impMap, ok = impSlice[0].(map[string]interface{}); !ok {
+		return jsonData, fmt.Errorf("unexpected type for imp[0] found in json data")
+	}
+
+	nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native")
+	if !ok {
+		return jsonData, fmt.Errorf("unable to find imp[0].native in json data")
+	}
+
+	nativeMap["request_native"] = requestNative
+
+	if jsonReEncoded, err := json.Marshal(jsonMap); err == nil {
+		return jsonReEncoded, nil
+	} else {
+		return nil, fmt.Errorf("unable to encode json data (%v)", err)
+	}
+}
+
 func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
 	if response.StatusCode == http.StatusNoContent {
 		return nil, nil
@@ -1052,11 +964,13 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external
 
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
 
-	bidType := openrtb_ext.BidTypeBanner
+	bidType := openrtb_ext.BidTypeNative
 
 	isVideo := isVideo(bidReq.Imp[0])
 	if isVideo {
 		bidType = openrtb_ext.BidTypeVideo
+	} else if bidReq.Imp[0].Banner != nil {
+		bidType = openrtb_ext.BidTypeBanner
 	}
 
 	impToCpmOverride := mapImpIdToCpmOverride(internalRequest.Imp)
@@ -1089,13 +1003,32 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, external
 				if bid.ID == "0" {
 					bid.ID = bidResp.BidID
 				}
+
+				resolvedAdm := resolveAdm(bid)
+				if len(resolvedAdm) > 0 {
+					bid.AdM = resolvedAdm
+				}
+
+				var ortbBid openrtb2.Bid // `targetStruct` can be anything of your choice
+
+				rubiconBidAsBytes, _ := json.Marshal(bid)
+				if len(rubiconBidAsBytes) > 0 {
+					err = json.Unmarshal(rubiconBidAsBytes, &ortbBid)
+					if err != nil {
+						return nil, []error{err}
+					}
+				}
+
 				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
-					Bid:     &bid,
+					Bid:     &ortbBid,
 					BidType: bidType,
 				})
 			}
 		}
 	}
+	if bidResp.Cur != "" {
+		bidResponse.Currency = bidResp.Cur
+	}
 
 	return bidResponse, nil
 }
@@ -1118,6 +1051,21 @@ func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 {
 	return impIdToCmpOverride
 }
 
+func resolveAdm(bid rubiconBid) string {
+	var bidAdm = bid.AdM
+	if len(bidAdm) > 0 {
+		return bidAdm
+	}
+
+	admObject := bid.AdmObject
+	admObjectAsBytes, err := json.Marshal(&admObject)
+	if err != nil {
+		return ""
+	}
+
+	return string(admObjectAsBytes)
+}
+
 func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 {
 	var bidRequestExt bidRequestExt
 	if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil {
@@ -1127,7 +1075,7 @@ func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 {
 	return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride
 }
 
-func updateBidExtWithMetaNetworkId(bid openrtb2.Bid, buyer int) json.RawMessage {
+func updateBidExtWithMetaNetworkId(bid rubiconBid, buyer int) json.RawMessage {
 	if buyer <= 0 {
 		return nil
 	}
diff --git a/adapters/smaato/native.go b/adapters/smaato/native.go
new file mode 100644
index 00000000..d0d40d35
--- /dev/null
+++ b/adapters/smaato/native.go
@@ -0,0 +1,27 @@
+package smaato
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/prebid/prebid-server/errortypes"
+)
+
+type nativeAd struct {
+	Native json.RawMessage `json:"native"`
+}
+
+func extractAdmNative(adMarkup string) (string, error) {
+	var nativeAd nativeAd
+	if err := json.Unmarshal([]byte(adMarkup), &nativeAd); err != nil {
+		return "", &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup),
+		}
+	}
+	adm, err := json.Marshal(&nativeAd.Native)
+	if err != nil {
+		return "", &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup),
+		}
+	}
+	return string(adm), nil
+}
diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go
index 244aeb9b..7981dda2 100644
--- a/adapters/smaato/smaato.go
+++ b/adapters/smaato/smaato.go
@@ -17,7 +17,7 @@ import (
 	"github.com/prebid/prebid-server/util/timeutil"
 )
 
-const clientVersion = "prebid_server_0.4"
+const clientVersion = "prebid_server_0.5"
 
 type adMarkupType string
 
@@ -25,6 +25,7 @@ const (
 	smtAdTypeImg       adMarkupType = "Img"
 	smtAdTypeRichmedia adMarkupType = "Richmedia"
 	smtAdTypeVideo     adMarkupType = "Video"
+	smtAdTypeNative    adMarkupType = "Native"
 )
 
 // adapter describes a Smaato prebid server adapter.
@@ -187,20 +188,29 @@ func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([]
 }
 
 func splitImpressionsByMediaType(imp *openrtb2.Imp) ([]openrtb2.Imp, error) {
-	if imp.Banner == nil && imp.Video == nil {
-		return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."}
+	if imp.Banner == nil && imp.Video == nil && imp.Native == nil {
+		return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner, Video and Native."}
 	}
 
-	imps := make([]openrtb2.Imp, 0, 2)
+	imps := make([]openrtb2.Imp, 0, 3)
 
 	if imp.Banner != nil {
 		impCopy := *imp
 		impCopy.Video = nil
+		impCopy.Native = nil
 		imps = append(imps, impCopy)
 	}
 
 	if imp.Video != nil {
+		impCopy := *imp
+		impCopy.Banner = nil
+		impCopy.Native = nil
+		imps = append(imps, impCopy)
+	}
+
+	if imp.Native != nil {
 		imp.Banner = nil
+		imp.Video = nil
 		imps = append(imps, *imp)
 	}
 
@@ -258,6 +268,8 @@ func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkup
 		return smtAdTypeRichmedia, nil
 	} else if strings.HasPrefix(adMarkup, `<?xml`) {
 		return smtAdTypeVideo, nil
+	} else if strings.HasPrefix(adMarkup, `{"native":`) {
+		return smtAdTypeNative, nil
 	} else {
 		return "", &errortypes.BadServerResponse{
 			Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup),
@@ -287,6 +299,8 @@ func renderAdMarkup(adMarkupType adMarkupType, adMarkup string) (string, error)
 		return extractAdmRichMedia(adMarkup)
 	case smtAdTypeVideo:
 		return adMarkup, nil
+	case smtAdTypeNative:
+		return extractAdmNative(adMarkup)
 	default:
 		return "", &errortypes.BadServerResponse{
 			Message: fmt.Sprintf("Unknown markup type %s.", adMarkupType),
@@ -302,6 +316,8 @@ func convertAdMarkupTypeToMediaType(adMarkupType adMarkupType) (openrtb_ext.BidT
 		return openrtb_ext.BidTypeBanner, nil
 	case smtAdTypeVideo:
 		return openrtb_ext.BidTypeVideo, nil
+	case smtAdTypeNative:
+		return openrtb_ext.BidTypeNative, nil
 	default:
 		return "", &errortypes.BadServerResponse{
 			Message: fmt.Sprintf("Unknown markup type %s.", adMarkupType),
@@ -459,7 +475,7 @@ func setImpForAdspace(imp *openrtb2.Imp) error {
 		return nil
 	}
 
-	if imp.Video != nil {
+	if imp.Video != nil || imp.Native != nil {
 		imp.TagID = adSpaceID
 		imp.Ext = nil
 		return nil
diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go
index fc7e1da3..9a44f510 100644
--- a/adapters/yieldone/yieldone.go
+++ b/adapters/yieldone/yieldone.go
@@ -86,6 +86,9 @@ func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 			})
 		}
 	}
+	if bidResp.Cur != "" {
+		bidResponse.Currency = bidResp.Cur
+	}
 	return bidResponse, nil
 
 }
diff --git a/amp/parse.go b/amp/parse.go
index 9fce681d..bbbcee79 100644
--- a/amp/parse.go
+++ b/amp/parse.go
@@ -18,18 +18,19 @@ import (
 
 // Params defines the parameters of an AMP request.
 type Params struct {
-	Account         string
-	CanonicalURL    string
-	Consent         string
-	ConsentType     int64
-	Debug           bool
-	GdprApplies     *bool
-	Origin          string
-	Size            Size
-	Slot            string
-	StoredRequestID string
-	Targeting       string
-	Timeout         *uint64
+	Account           string
+	AdditionalConsent string
+	CanonicalURL      string
+	Consent           string
+	ConsentType       int64
+	Debug             bool
+	GdprApplies       *bool
+	Origin            string
+	Size              Size
+	Slot              string
+	StoredRequestID   string
+	Targeting         string
+	Timeout           *uint64
 }
 
 // Size defines size information of an AMP request.
@@ -154,12 +155,13 @@ func ParseParams(httpRequest *http.Request) (Params, error) {
 	}
 
 	params := Params{
-		Account:      query.Get("account"),
-		CanonicalURL: query.Get("curl"),
-		Consent:      chooseConsent(query.Get("consent_string"), query.Get("gdpr_consent")),
-		ConsentType:  parseInt(query.Get("consent_type")),
-		Debug:        query.Get("debug") == "1",
-		Origin:       query.Get("__amp_source_origin"),
+		Account:           query.Get("account"),
+		AdditionalConsent: query.Get("addtl_consent"),
+		CanonicalURL:      query.Get("curl"),
+		Consent:           chooseConsent(query.Get("consent_string"), query.Get("gdpr_consent")),
+		ConsentType:       parseInt(query.Get("consent_type")),
+		Debug:             query.Get("debug") == "1",
+		Origin:            query.Get("__amp_source_origin"),
 		Size: Size{
 			Height:         parseInt(query.Get("h")),
 			Multisize:      parseMultisize(query.Get("ms")),
diff --git a/analytics/config/config.go b/analytics/config/config.go
index ae33e464..557fec36 100644
--- a/analytics/config/config.go
+++ b/analytics/config/config.go
@@ -10,7 +10,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 )
 
-//Modules that need to be logged to need to be initialized here
+// Modules that need to be logged to need to be initialized here
 func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule {
 	modules := make(enabledAnalytics, 0)
 	if len(analytics.File.Filename) > 0 {
@@ -40,7 +40,7 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule {
 	return modules
 }
 
-//Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface
+// Collection of all the correctly configured analytics modules - implements the PBSAnalyticsModule interface
 type enabledAnalytics []analytics.PBSAnalyticsModule
 
 func (ea enabledAnalytics) LogAuctionObject(ao *analytics.AuctionObject) {
diff --git a/analytics/core.go b/analytics/core.go
index d3965597..e2d5a071 100644
--- a/analytics/core.go
+++ b/analytics/core.go
@@ -25,7 +25,7 @@ type PBSAnalyticsModule interface {
 	LogNotificationEventObject(*NotificationEvent)
 }
 
-//Loggable object of a transaction at /openrtb2/auction endpoint
+// Loggable object of a transaction at /openrtb2/auction endpoint
 type AuctionObject struct {
 	Status    int
 	Errors    []error
@@ -35,7 +35,7 @@ type AuctionObject struct {
 	StartTime time.Time
 }
 
-//Loggable object of a transaction at /openrtb2/amp endpoint
+// Loggable object of a transaction at /openrtb2/amp endpoint
 type AmpObject struct {
 	Status             int
 	Errors             []error
@@ -46,7 +46,7 @@ type AmpObject struct {
 	StartTime          time.Time
 }
 
-//Loggable object of a transaction at /openrtb2/video endpoint
+// Loggable object of a transaction at /openrtb2/video endpoint
 type VideoObject struct {
 	Status        int
 	Errors        []error
@@ -57,7 +57,7 @@ type VideoObject struct {
 	StartTime     time.Time
 }
 
-//Loggable object of a transaction at /setuid
+// Loggable object of a transaction at /setuid
 type SetUIDObject struct {
 	Status  int
 	Bidder  string
@@ -66,7 +66,7 @@ type SetUIDObject struct {
 	Success bool
 }
 
-//Loggable object of a transaction at /cookie_sync
+// Loggable object of a transaction at /cookie_sync
 type CookieSyncObject struct {
 	Status       int
 	Errors       []error
diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go
index 43853382..d6a03ea2 100644
--- a/analytics/filesystem/file_module.go
+++ b/analytics/filesystem/file_module.go
@@ -20,12 +20,12 @@ const (
 	NOTIFICATION_EVENT RequestType = "/event"
 )
 
-//Module that can perform transactional logging
+// Module that can perform transactional logging
 type FileLogger struct {
 	Logger *glog.Logger
 }
 
-//Writes AuctionObject to file
+// Writes AuctionObject to file
 func (f *FileLogger) LogAuctionObject(ao *analytics.AuctionObject) {
 	//Code to parse the object and log in a way required
 	var b bytes.Buffer
@@ -34,7 +34,7 @@ func (f *FileLogger) LogAuctionObject(ao *analytics.AuctionObject) {
 	f.Logger.Flush()
 }
 
-//Writes VideoObject to file
+// Writes VideoObject to file
 func (f *FileLogger) LogVideoObject(vo *analytics.VideoObject) {
 	//Code to parse the object and log in a way required
 	var b bytes.Buffer
@@ -43,7 +43,7 @@ func (f *FileLogger) LogVideoObject(vo *analytics.VideoObject) {
 	f.Logger.Flush()
 }
 
-//Logs SetUIDObject to file
+// Logs SetUIDObject to file
 func (f *FileLogger) LogSetUIDObject(so *analytics.SetUIDObject) {
 	//Code to parse the object and log in a way required
 	var b bytes.Buffer
@@ -52,7 +52,7 @@ func (f *FileLogger) LogSetUIDObject(so *analytics.SetUIDObject) {
 	f.Logger.Flush()
 }
 
-//Logs CookieSyncObject to file
+// Logs CookieSyncObject to file
 func (f *FileLogger) LogCookieSyncObject(cso *analytics.CookieSyncObject) {
 	//Code to parse the object and log in a way required
 	var b bytes.Buffer
@@ -61,7 +61,7 @@ func (f *FileLogger) LogCookieSyncObject(cso *analytics.CookieSyncObject) {
 	f.Logger.Flush()
 }
 
-//Logs AmpObject to file
+// Logs AmpObject to file
 func (f *FileLogger) LogAmpObject(ao *analytics.AmpObject) {
 	if ao == nil {
 		return
@@ -73,7 +73,7 @@ func (f *FileLogger) LogAmpObject(ao *analytics.AmpObject) {
 	f.Logger.Flush()
 }
 
-//Logs NotificationEvent to file
+// Logs NotificationEvent to file
 func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent) {
 	if ne == nil {
 		return
@@ -85,7 +85,7 @@ func (f *FileLogger) LogNotificationEventObject(ne *analytics.NotificationEvent)
 	f.Logger.Flush()
 }
 
-//Method to initialize the analytic module
+// Method to initialize the analytic module
 func NewFileLogger(filename string) (analytics.PBSAnalyticsModule, error) {
 	options := glog.LogOptions{
 		File:  filename,
diff --git a/currency/rates.go b/currency/rates.go
index f1cc19fc..f36eddfa 100644
--- a/currency/rates.go
+++ b/currency/rates.go
@@ -21,10 +21,10 @@ func NewRates(conversions map[string]map[string]float64) *Rates {
 }
 
 // GetRate returns the conversion rate between two currencies or:
-//  - An error if one of the currency strings is not well-formed
-//  - An error if any of the currency strings is not a recognized currency code.
-//  - A ConversionNotFoundError in case the conversion rate between the two
-//    given currencies is not in the currencies rates map
+//   - An error if one of the currency strings is not well-formed
+//   - An error if any of the currency strings is not a recognized currency code.
+//   - A ConversionNotFoundError in case the conversion rate between the two
+//     given currencies is not in the currencies rates map
 func (r *Rates) GetRate(from, to string) (float64, error) {
 	var err error
 	fromUnit, err := currency.ParseISO(from)
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 1c012f11..addb2c1d 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -461,6 +461,10 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2
 		req.Imp[0].TagID = ampParams.Slot
 	}
 
+	if err := setConsentedProviders(req, ampParams); err != nil {
+		return []error{err}
+	}
+
 	policyWriter, policyWriterErr := amp.ReadPolicy(ampParams, deps.cfg.GDPR.Enabled)
 	if policyWriterErr != nil {
 		return []error{policyWriterErr}
@@ -480,6 +484,46 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2
 	return nil
 }
 
+// setConsentedProviders sets the addtl_consent value to user.ext.ConsentedProvidersSettings.consented_providers
+// in its orginal Google Additional Consent string format and user.ext.consented_providers_settings.consented_providers
+// that is an array of ints that contains the elements found in addtl_consent
+func setConsentedProviders(req *openrtb2.BidRequest, ampParams amp.Params) error {
+	if len(ampParams.AdditionalConsent) > 0 {
+		reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
+
+		userExt, err := reqWrap.GetUserExt()
+		if err != nil {
+			return err
+		}
+
+		// Parse addtl_consent, that is supposed to come formatted as a Google Additional Consent string, into array of ints
+		consentedProvidersList := openrtb_ext.ParseConsentedProvidersString(ampParams.AdditionalConsent)
+
+		// Set user.ext.consented_providers_settings.consented_providers if elements where found
+		if len(consentedProvidersList) > 0 {
+			cps := userExt.GetConsentedProvidersSettingsOut()
+			if cps == nil {
+				cps = &openrtb_ext.ConsentedProvidersSettingsOut{}
+			}
+			cps.ConsentedProvidersList = append(cps.ConsentedProvidersList, consentedProvidersList...)
+			userExt.SetConsentedProvidersSettingsOut(cps)
+		}
+
+		// Copy addtl_consent into user.ext.ConsentedProvidersSettings.consented_providers as is
+		cps := userExt.GetConsentedProvidersSettingsIn()
+		if cps == nil {
+			cps = &openrtb_ext.ConsentedProvidersSettingsIn{}
+		}
+		cps.ConsentedProvidersString = ampParams.AdditionalConsent
+		userExt.SetConsentedProvidersSettingsIn(cps)
+
+		if err := reqWrap.RebuildRequest(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // setTargeting merges "targeting" to imp[0].ext.data
 func setTargeting(req *openrtb2.BidRequest, targeting string) error {
 	if len(targeting) == 0 {
diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json
new file mode 100644
index 00000000..7247e2df
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/addtl-consent-through-query.json
@@ -0,0 +1,92 @@
+{
+  "description": "Additional consent providers string passed through query: expect user.ext.ConsentedProvidersSettings.consented_providers to be set to the addtl_consent string",
+  "query": "tag_id=101&addtl_consent=1~1.35.41.101",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 15}
+    ]
+  },
+  "mockBidRequest": {
+    "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+    "site": {"page": "prebid.org"},
+    "imp": [
+      {
+        "id": "/19968336/header-bid-tag-0",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedValidatedBidRequest": {
+    "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+    "site": {
+      "page": "prebid.org",
+      "ext": {"amp":1}
+    },
+    "device": {
+      "ip":"192.0.2.1"
+    },
+    "at": 1,
+    "imp": [
+      {
+        "id": "/19968336/header-bid-tag-0",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "secure": 1,
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              }
+            }
+          }
+        }
+      }
+    ],
+    "user": {
+      "ext": {
+        "ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"},
+        "consented_providers_settings":{"consented_providers":[1,35,41,101]}
+      }
+    },
+    "ext": {"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}}
+  },
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "appnexus",
+      "hb_bidder_appnexus": "appnexus",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_appnex": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_appnexus": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_appnex": "/pbcache/endpoint",
+      "hb_pb": "15.00",
+      "hb_pb_appnexus": "15.00"
+    },
+    "warnings": {
+      "general": [
+        {"code": 10002, "message": "debug turned off for account"}
+      ]
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 49458470..0b59c19b 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -89,27 +89,27 @@ func NewVideoEndpoint(
 }
 
 /*
-1. Parse "storedrequestid" field from simplified endpoint request body.
-2. If config flag to require that field is set (which it will be for us) and this field is not given then error out here.
-3. Load the stored request JSON for the given storedrequestid, if the id was invalid then error out here.
-4. Use "json-patch" 3rd party library to merge the request body JSON data into the stored request JSON data.
-5. Unmarshal the merged JSON data into a Go structure.
-6. Add fields from merged JSON data that correspond to an OpenRTB request into the OpenRTB bid request we are building.
-	a. Unmarshal certain OpenRTB defined structs directly into the OpenRTB bid request.
-	b. In cases where customized logic is needed just copy/fill the fields in directly.
-7. Call setFieldsImplicitly from auction.go to get basic data from the HTTP request into an OpenRTB bid request to start building the OpenRTB bid request.
-8. Loop through ad pods to build array of Imps into OpenRTB request, for each pod:
-	a. Load the stored impression to use as the basis for impressions generated for this pod from the configid field.
-	b. NumImps = adpoddurationsec / MIN_VALUE(allowedDurations)
-	c. Build impression array for this pod:
-		I.Create array of NumImps entries initialized to the base impression loaded from the configid.
-			1. If requireexactdurations = true, iterate over allowdDurations and for (NumImps / len(allowedDurations)) number of Imps set minduration = maxduration = allowedDurations[i]
-			2. If requireexactdurations = false, set maxduration = MAX_VALUE(allowedDurations)
-		II. Set Imp.id field to "podX_Y" where X is the pod index and Y is the impression index within this pod.
-	d. Append impressions for this pod to the overall list of impressions in the OpenRTB bid request.
-9. Call validateRequest() function from auction.go to validate the generated request.
-10. Call HoldAuction() function to run the auction for the OpenRTB bid request that was built in the previous step.
-11. Build proper response format.
+ 1. Parse "storedrequestid" field from simplified endpoint request body.
+ 2. If config flag to require that field is set (which it will be for us) and this field is not given then error out here.
+ 3. Load the stored request JSON for the given storedrequestid, if the id was invalid then error out here.
+ 4. Use "json-patch" 3rd party library to merge the request body JSON data into the stored request JSON data.
+ 5. Unmarshal the merged JSON data into a Go structure.
+ 6. Add fields from merged JSON data that correspond to an OpenRTB request into the OpenRTB bid request we are building.
+    a. Unmarshal certain OpenRTB defined structs directly into the OpenRTB bid request.
+    b. In cases where customized logic is needed just copy/fill the fields in directly.
+ 7. Call setFieldsImplicitly from auction.go to get basic data from the HTTP request into an OpenRTB bid request to start building the OpenRTB bid request.
+ 8. Loop through ad pods to build array of Imps into OpenRTB request, for each pod:
+    a. Load the stored impression to use as the basis for impressions generated for this pod from the configid field.
+    b. NumImps = adpoddurationsec / MIN_VALUE(allowedDurations)
+    c. Build impression array for this pod:
+    I.Create array of NumImps entries initialized to the base impression loaded from the configid.
+ 1. If requireexactdurations = true, iterate over allowdDurations and for (NumImps / len(allowedDurations)) number of Imps set minduration = maxduration = allowedDurations[i]
+ 2. If requireexactdurations = false, set maxduration = MAX_VALUE(allowedDurations)
+    II. Set Imp.id field to "podX_Y" where X is the pod index and Y is the impression index within this pod.
+    d. Append impressions for this pod to the overall list of impressions in the OpenRTB bid request.
+ 9. Call validateRequest() function from auction.go to validate the generated request.
+ 10. Call HoldAuction() function to run the auction for the OpenRTB bid request that was built in the previous step.
+ 11. Build proper response format.
 */
 func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
 	start := time.Now()
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 1d34708d..b757ea76 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -30,7 +30,6 @@ import (
 	"github.com/prebid/prebid-server/adapters/aja"
 	"github.com/prebid/prebid-server/adapters/algorix"
 	"github.com/prebid/prebid-server/adapters/amx"
-	"github.com/prebid/prebid-server/adapters/andbeyondmedia"
 	"github.com/prebid/prebid-server/adapters/apacdex"
 	"github.com/prebid/prebid-server/adapters/applogy"
 	"github.com/prebid/prebid-server/adapters/appnexus"
@@ -41,9 +40,11 @@ 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/beyondmedia"
 	"github.com/prebid/prebid-server/adapters/bidmachine"
 	"github.com/prebid/prebid-server/adapters/bidmyadz"
 	"github.com/prebid/prebid-server/adapters/bidscube"
+	"github.com/prebid/prebid-server/adapters/bidstack"
 	"github.com/prebid/prebid-server/adapters/bizzclick"
 	"github.com/prebid/prebid-server/adapters/bliink"
 	"github.com/prebid/prebid-server/adapters/blue"
@@ -185,7 +186,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderAJA:               aja.Builder,
 		openrtb_ext.BidderAlgorix:           algorix.Builder,
 		openrtb_ext.BidderAMX:               amx.Builder,
-		openrtb_ext.BidderAndBeyondMedia:    andbeyondmedia.Builder,
 		openrtb_ext.BidderApacdex:           apacdex.Builder,
 		openrtb_ext.BidderApplogy:           applogy.Builder,
 		openrtb_ext.BidderAppnexus:          appnexus.Builder,
@@ -196,9 +196,11 @@ 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.BidderBeyondMedia:       beyondmedia.Builder,
 		openrtb_ext.BidderBidmachine:        bidmachine.Builder,
 		openrtb_ext.BidderBidmyadz:          bidmyadz.Builder,
 		openrtb_ext.BidderBidsCube:          bidscube.Builder,
+		openrtb_ext.BidderBidstack:          bidstack.Builder,
 		openrtb_ext.BidderBizzclick:         bizzclick.Builder,
 		openrtb_ext.BidderBliink:            bliink.Builder,
 		openrtb_ext.BidderBlue:              blue.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index bd252c0c..544fe936 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -85,10 +85,11 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam
 // GetDisabledBiddersErrorMessages returns a map of error messages for disabled bidders.
 func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string {
 	disabledBidders := map[string]string{
-		"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
-		"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
-		"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
-		"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+		"lifestreet":     `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+		"adagio":         `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
+		"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+		"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+		"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 	}
 
 	for name, info := range infos {
diff --git a/firstpartydata/first_party_data.go b/firstpartydata/first_party_data.go
index 13d9dddc..bdd7e766 100644
--- a/firstpartydata/first_party_data.go
+++ b/firstpartydata/first_party_data.go
@@ -98,7 +98,7 @@ func ExtractGlobalFPD(req *openrtb_ext.RequestWrapper) (map[string][]byte, error
 	return fpdReqData, nil
 }
 
-//ExtractOpenRtbGlobalFPD extracts and deletes user.data and {app/site}.content.data from request
+// ExtractOpenRtbGlobalFPD extracts and deletes user.data and {app/site}.content.data from request
 func ExtractOpenRtbGlobalFPD(bidRequest *openrtb2.BidRequest) map[string][]openrtb2.Data {
 
 	openRtbGlobalFPD := make(map[string][]openrtb2.Data, 3)
@@ -121,7 +121,7 @@ func ExtractOpenRtbGlobalFPD(bidRequest *openrtb2.BidRequest) map[string][]openr
 
 }
 
-//ResolveFPD consolidates First Party Data from different sources and returns valid FPD that will be applied to bidders later or returns errors
+// ResolveFPD consolidates First Party Data from different sources and returns valid FPD that will be applied to bidders later or returns errors
 func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, biddersWithGlobalFPD []string) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) {
 	var errL []error
 
@@ -248,7 +248,7 @@ func unmarshalJSONToContent(input json.RawMessage) (*openrtb2.Content, error) {
 	return &result, err
 }
 
-//resolveExtension inserts remaining {site/app/user} attributes back to {site/app/user}.ext.data
+// resolveExtension inserts remaining {site/app/user} attributes back to {site/app/user}.ext.data
 func resolveExtension(fpdConfig map[string]json.RawMessage, originalExt json.RawMessage) ([]byte, error) {
 	resExt := originalExt
 	var err error
@@ -603,7 +603,7 @@ func buildExtData(data []byte) []byte {
 	return res
 }
 
-//ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig
+// ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig
 func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) {
 
 	fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2)
@@ -642,7 +642,7 @@ func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.Bid
 
 }
 
-//ExtractFPDForBidders extracts FPD data from request if specified
+// ExtractFPDForBidders extracts FPD data from request if specified
 func ExtractFPDForBidders(req *openrtb_ext.RequestWrapper) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) {
 
 	reqExt, err := req.GetRequestExt()
diff --git a/go.mod b/go.mod
index fe6cab96..be5ea481 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/prebid/prebid-server
 
-go 1.16
+go 1.19
 
 require (
 	github.com/DATA-DOG/go-sqlmock v1.5.0
@@ -23,17 +23,51 @@ require (
 	github.com/prometheus/client_model v0.2.0
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
 	github.com/rs/cors v1.8.2
-	github.com/sergi/go-diff v1.2.0 // indirect
 	github.com/spf13/viper v1.12.0
 	github.com/stretchr/testify v1.7.1
 	github.com/vrischmann/go-metrics-influxdb v0.1.1
 	github.com/xeipuuv/gojsonschema v1.2.0
 	github.com/yudai/gojsondiff v1.0.0
-	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
-	github.com/yudai/pp v2.0.1+incompatible // indirect
 	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
 	golang.org/x/text v0.3.7
 	google.golang.org/grpc v1.46.2
 	gopkg.in/evanphx/json-patch.v4 v4.12.0
 	gopkg.in/yaml.v3 v3.0.1
 )
+
+require (
+	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/fsnotify/fsnotify v1.5.4 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d // indirect
+	github.com/magiconair/properties v1.8.6 // indirect
+	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/mitchellh/reflectwalk v1.0.2 // indirect
+	github.com/pelletier/go-toml v1.9.5 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/prometheus/common v0.32.1 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
+	github.com/sergi/go-diff v1.2.0 // indirect
+	github.com/spf13/afero v1.8.2 // indirect
+	github.com/spf13/cast v1.5.0 // indirect
+	github.com/spf13/jwalterweatherman v1.1.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/stretchr/objx v0.1.1 // indirect
+	github.com/subosito/gotenv v1.3.0 // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
+	github.com/yudai/pp v2.0.1+incompatible // indirect
+	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
+	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
+	google.golang.org/protobuf v1.28.0 // indirect
+	gopkg.in/ini.v1 v1.66.4 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+)
diff --git a/go.sum b/go.sum
index bdacb166..5cce1e88 100644
--- a/go.sum
+++ b/go.sum
@@ -29,18 +29,12 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW
 cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
 cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
 cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
-cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
-cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
-cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
-cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
-cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
@@ -90,7 +84,6 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
 github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@@ -118,13 +111,11 @@ github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -142,7 +133,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
-github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@@ -219,7 +209,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -247,14 +236,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
-github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
-github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
-github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
-github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
 github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -264,7 +248,6 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9
 github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
 github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -293,7 +276,6 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
 github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
-github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
 github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
 github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@@ -327,11 +309,9 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
@@ -419,7 +399,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -446,7 +425,6 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
 github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -454,7 +432,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
 github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
-github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -512,12 +489,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
-go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
-go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -629,11 +602,6 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -653,9 +621,6 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -667,7 +632,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -747,16 +711,9 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -835,8 +792,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -870,14 +825,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
 google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
 google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
-google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
-google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
-google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
-google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
-google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
-google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
-google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
-google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -951,20 +898,7 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
-google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
 google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -997,7 +931,6 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
 google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
 google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
@@ -1044,7 +977,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1057,4 +989,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go
index 4079a785..ac49352d 100644
--- a/metrics/config/metrics.go
+++ b/metrics/config/metrics.go
@@ -86,7 +86,7 @@ func (me *MultiMetricsEngine) RecordConnectionClose(success bool) {
 	}
 }
 
-//RecordsImps records imps with imp types across all metric engines
+// RecordsImps records imps with imp types across all metric engines
 func (me *MultiMetricsEngine) RecordImps(implabels metrics.ImpLabels) {
 	for _, thisME := range *me {
 		thisME.RecordImps(implabels)
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index a1795b32..aba2771b 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -110,7 +110,6 @@ const (
 	BidderAJA               BidderName = "aja"
 	BidderAlgorix           BidderName = "algorix"
 	BidderAMX               BidderName = "amx"
-	BidderAndBeyondMedia    BidderName = "andbeyondmedia"
 	BidderApacdex           BidderName = "apacdex"
 	BidderApplogy           BidderName = "applogy"
 	BidderAppnexus          BidderName = "appnexus"
@@ -121,9 +120,11 @@ const (
 	BidderBeachfront        BidderName = "beachfront"
 	BidderBeintoo           BidderName = "beintoo"
 	BidderBetween           BidderName = "between"
+	BidderBeyondMedia       BidderName = "beyondmedia"
 	BidderBidmachine        BidderName = "bidmachine"
 	BidderBidmyadz          BidderName = "bidmyadz"
 	BidderBidsCube          BidderName = "bidscube"
+	BidderBidstack          BidderName = "bidstack"
 	BidderBizzclick         BidderName = "bizzclick"
 	BidderBliink            BidderName = "bliink"
 	BidderBlue              BidderName = "blue"
@@ -273,7 +274,6 @@ func CoreBidderNames() []BidderName {
 		BidderAJA,
 		BidderAlgorix,
 		BidderAMX,
-		BidderAndBeyondMedia,
 		BidderApacdex,
 		BidderApplogy,
 		BidderAppnexus,
@@ -284,9 +284,11 @@ func CoreBidderNames() []BidderName {
 		BidderBeachfront,
 		BidderBeintoo,
 		BidderBetween,
+		BidderBeyondMedia,
 		BidderBidmachine,
 		BidderBidmyadz,
 		BidderBidsCube,
+		BidderBidstack,
 		BidderBizzclick,
 		BidderBliink,
 		BidderBlue,
diff --git a/openrtb_ext/imp_andbeyondmedia.go b/openrtb_ext/imp_beyondmedia.go
similarity index 64%
rename from openrtb_ext/imp_andbeyondmedia.go
rename to openrtb_ext/imp_beyondmedia.go
index 68b65a3c..d9cb1ef8 100644
--- a/openrtb_ext/imp_andbeyondmedia.go
+++ b/openrtb_ext/imp_beyondmedia.go
@@ -1,5 +1,5 @@
 package openrtb_ext
 
-type ImpExtAndBeyondMedia struct {
+type ImpExtBeyondMedia struct {
 	PlacementID string `json:"placementId"`
 }
diff --git a/openrtb_ext/imp_bidstack.go b/openrtb_ext/imp_bidstack.go
new file mode 100644
index 00000000..c3451c81
--- /dev/null
+++ b/openrtb_ext/imp_bidstack.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtBidstack struct {
+	PublisherID string `json:"publisherId"`
+}
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
index 6a2618bb..5e336e01 100644
--- a/openrtb_ext/request_wrapper.go
+++ b/openrtb_ext/request_wrapper.go
@@ -42,7 +42,11 @@ type RequestWrapper struct {
 	sourceExt   *SourceExt
 }
 
-const jsonEmptyObjectLength = 2
+const (
+	jsonEmptyObjectLength               = 2
+	ConsentedProvidersSettingsStringKey = "ConsentedProvidersSettings"
+	ConsentedProvidersSettingsListKey   = "consented_providers_settings"
+)
 
 // LenImp returns the number of impressions without causing the creation of ImpWrapper objects.
 func (rw *RequestWrapper) LenImp() int {
@@ -334,14 +338,18 @@ func (rw *RequestWrapper) rebuildSourceExt() error {
 // ---------------------------------------------------------------
 
 type UserExt struct {
-	ext          map[string]json.RawMessage
-	extDirty     bool
-	consent      *string
-	consentDirty bool
-	prebid       *ExtUserPrebid
-	prebidDirty  bool
-	eids         *[]openrtb2.EID
-	eidsDirty    bool
+	ext                                map[string]json.RawMessage
+	extDirty                           bool
+	consent                            *string
+	consentDirty                       bool
+	prebid                             *ExtUserPrebid
+	prebidDirty                        bool
+	eids                               *[]openrtb2.EID
+	eidsDirty                          bool
+	consentedProvidersSettingsIn       *ConsentedProvidersSettingsIn
+	consentedProvidersSettingsInDirty  bool
+	consentedProvidersSettingsOut      *ConsentedProvidersSettingsOut
+	consentedProvidersSettingsOutDirty bool
 }
 
 func (ue *UserExt) unmarshal(extJson json.RawMessage) error {
@@ -382,6 +390,20 @@ func (ue *UserExt) unmarshal(extJson json.RawMessage) error {
 		}
 	}
 
+	if consentedProviderSettingsJson, hasCPSettings := ue.ext[ConsentedProvidersSettingsStringKey]; hasCPSettings {
+		ue.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{}
+		if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsIn); err != nil {
+			return err
+		}
+	}
+
+	if consentedProviderSettingsJson, hasCPSettings := ue.ext[ConsentedProvidersSettingsListKey]; hasCPSettings {
+		ue.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{}
+		if err := json.Unmarshal(consentedProviderSettingsJson, ue.consentedProvidersSettingsOut); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -416,6 +438,40 @@ func (ue *UserExt) marshal() (json.RawMessage, error) {
 		ue.prebidDirty = false
 	}
 
+	if ue.consentedProvidersSettingsInDirty {
+		if ue.consentedProvidersSettingsIn != nil {
+			cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsIn)
+			if err != nil {
+				return nil, err
+			}
+			if len(cpSettingsJson) > jsonEmptyObjectLength {
+				ue.ext[ConsentedProvidersSettingsStringKey] = json.RawMessage(cpSettingsJson)
+			} else {
+				delete(ue.ext, ConsentedProvidersSettingsStringKey)
+			}
+		} else {
+			delete(ue.ext, ConsentedProvidersSettingsStringKey)
+		}
+		ue.consentedProvidersSettingsInDirty = false
+	}
+
+	if ue.consentedProvidersSettingsOutDirty {
+		if ue.consentedProvidersSettingsOut != nil {
+			cpSettingsJson, err := json.Marshal(ue.consentedProvidersSettingsOut)
+			if err != nil {
+				return nil, err
+			}
+			if len(cpSettingsJson) > jsonEmptyObjectLength {
+				ue.ext[ConsentedProvidersSettingsListKey] = json.RawMessage(cpSettingsJson)
+			} else {
+				delete(ue.ext, ConsentedProvidersSettingsListKey)
+			}
+		} else {
+			delete(ue.ext, ConsentedProvidersSettingsListKey)
+		}
+		ue.consentedProvidersSettingsOutDirty = false
+	}
+
 	if ue.eidsDirty {
 		if ue.eids != nil && len(*ue.eids) > 0 {
 			eidsJson, err := json.Marshal(ue.eids)
@@ -437,7 +493,7 @@ func (ue *UserExt) marshal() (json.RawMessage, error) {
 }
 
 func (ue *UserExt) Dirty() bool {
-	return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty
+	return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty || ue.consentedProvidersSettingsInDirty || ue.consentedProvidersSettingsOutDirty
 }
 
 func (ue *UserExt) GetExt() map[string]json.RawMessage {
@@ -466,6 +522,46 @@ func (ue *UserExt) SetConsent(consent *string) {
 	ue.consentDirty = true
 }
 
+// GetConsentedProvidersSettingsIn() returns a reference to a copy of ConsentedProvidersSettingsIn, a struct that
+// has a string field formatted as a Google's Additional Consent string
+func (ue *UserExt) GetConsentedProvidersSettingsIn() *ConsentedProvidersSettingsIn {
+	if ue.consentedProvidersSettingsIn == nil {
+		return nil
+	}
+	consentedProvidersSettingsIn := *ue.consentedProvidersSettingsIn
+	return &consentedProvidersSettingsIn
+}
+
+// SetConsentedProvidersSettingsIn() sets ConsentedProvidersSettingsIn, a struct that
+// has a string field formatted as a Google's Additional Consent string
+func (ue *UserExt) SetConsentedProvidersSettingsIn(cpSettings *ConsentedProvidersSettingsIn) {
+	ue.consentedProvidersSettingsIn = cpSettings
+	ue.consentedProvidersSettingsInDirty = true
+}
+
+// GetConsentedProvidersSettingsOut() returns a reference to a copy of ConsentedProvidersSettingsOut, a struct that
+// has an int array field listing Google's Additional Consent string elements
+func (ue *UserExt) GetConsentedProvidersSettingsOut() *ConsentedProvidersSettingsOut {
+	if ue.consentedProvidersSettingsOut == nil {
+		return nil
+	}
+	consentedProvidersSettingsOut := *ue.consentedProvidersSettingsOut
+	return &consentedProvidersSettingsOut
+}
+
+// SetConsentedProvidersSettingsIn() sets ConsentedProvidersSettingsOut, a struct that
+// has an int array field listing Google's Additional Consent string elements. This
+// function overrides an existing ConsentedProvidersSettingsOut object, if any
+func (ue *UserExt) SetConsentedProvidersSettingsOut(cpSettings *ConsentedProvidersSettingsOut) {
+	if cpSettings == nil {
+		return
+	}
+
+	ue.consentedProvidersSettingsOut = cpSettings
+	ue.consentedProvidersSettingsOutDirty = true
+	return
+}
+
 func (ue *UserExt) GetPrebid() *ExtUserPrebid {
 	if ue.prebid == nil {
 		return nil
diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go
index 00d5782d..0f2f2b03 100644
--- a/openrtb_ext/user.go
+++ b/openrtb_ext/user.go
@@ -1,6 +1,9 @@
 package openrtb_ext
 
 import (
+	"strconv"
+	"strings"
+
 	"github.com/prebid/openrtb/v17/openrtb2"
 )
 
@@ -10,6 +13,10 @@ type ExtUser struct {
 	// https://iabtechlab.com/wp-content/uploads/2018/02/OpenRTB_Advisory_GDPR_2018-02.pdf
 	Consent string `json:"consent,omitempty"`
 
+	ConsentedProvidersSettings *ConsentedProvidersSettingsIn `json:"ConsentedProvidersSettings,omitempty"`
+
+	ConsentedProvidersSettingsParsed *ConsentedProvidersSettingsOut `json:"consented_providers_settings,omitempty"`
+
 	Prebid *ExtUserPrebid `json:"prebid,omitempty"`
 
 	Eids []openrtb2.EID `json:"eids,omitempty"`
@@ -19,3 +26,37 @@ type ExtUser struct {
 type ExtUserPrebid struct {
 	BuyerUIDs map[string]string `json:"buyeruids,omitempty"`
 }
+
+type ConsentedProvidersSettingsIn struct {
+	ConsentedProvidersString string `json:"consented_providers,omitempty"`
+}
+
+type ConsentedProvidersSettingsOut struct {
+	ConsentedProvidersList []int `json:"consented_providers,omitempty"`
+}
+
+// ParseConsentedProvidersString takes a string formatted as Google's Additional Consent format and returns a list with its
+// elements. For instance, the following string "1~1.35.41.101" would result in []int{1, 35, 41, 101}
+func ParseConsentedProvidersString(cps string) []int {
+	// Additional Consent format version is separated from elements by the '~' character
+	parts := strings.Split(cps, "~")
+	if len(parts) != 2 {
+		return nil
+	}
+
+	// Split the individual elements
+	providerStringList := strings.Split(parts[1], ".")
+	if len(providerStringList) == 0 {
+		return nil
+	}
+
+	// Convert to ints and add to int array
+	var consentedProviders []int
+	for _, providerStr := range providerStringList {
+		if providerInt, err := strconv.Atoi(providerStr); err == nil {
+			consentedProviders = append(consentedProviders, providerInt)
+		}
+	}
+
+	return consentedProviders
+}
diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go
index e157c913..5ed1ddb0 100644
--- a/privacy/ccpa/consentwriter.go
+++ b/privacy/ccpa/consentwriter.go
@@ -17,10 +17,15 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error {
 		return nil
 	}
 	reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
-	if regsExt, err := reqWrap.GetRegExt(); err == nil {
-		regsExt.SetUSPrivacy(c.Consent)
-	} else {
-		return err
+
+	// Set consent string in USPrivacy
+	if c.Consent != "" {
+		if regsExt, err := reqWrap.GetRegExt(); err == nil {
+			regsExt.SetUSPrivacy(c.Consent)
+		} else {
+			return err
+		}
 	}
+
 	return reqWrap.RebuildRequest()
 }
diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go
index 9c885f8e..569dbeb1 100644
--- a/privacy/gdpr/consentwriter.go
+++ b/privacy/gdpr/consentwriter.go
@@ -1,8 +1,6 @@
 package gdpr
 
 import (
-	"encoding/json"
-
 	"github.com/prebid/openrtb/v17/openrtb2"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -15,42 +13,30 @@ type ConsentWriter struct {
 
 // Write mutates an OpenRTB bid request with the GDPR TCF consent.
 func (c ConsentWriter) Write(req *openrtb2.BidRequest) error {
+	if req == nil {
+		return nil
+	}
+	reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
+
 	if c.RegExtGDPR != nil {
-		reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
 		if regsExt, err := reqWrap.GetRegExt(); err == nil {
 			regsExt.SetGDPR(c.RegExtGDPR)
 		} else {
 			return err
 		}
-		if err := reqWrap.RebuildRequest(); err != nil {
-			return err
-		}
 	}
 
-	if c.Consent == "" {
-		return nil
-	}
-
-	if req.User == nil {
-		req.User = &openrtb2.User{}
+	if c.Consent != "" {
+		if userExt, err := reqWrap.GetUserExt(); err == nil {
+			userExt.SetConsent(&c.Consent)
+		} else {
+			return err
+		}
 	}
 
-	if req.User.Ext == nil {
-		ext, err := json.Marshal(openrtb_ext.ExtUser{Consent: c.Consent})
-		if err == nil {
-			req.User.Ext = ext
-		}
+	if err := reqWrap.RebuildRequest(); err != nil {
 		return err
 	}
 
-	var extMap map[string]interface{}
-	err := json.Unmarshal(req.User.Ext, &extMap)
-	if err == nil {
-		extMap["consent"] = c.Consent
-		ext, err := json.Marshal(extMap)
-		if err == nil {
-			req.User.Ext = ext
-		}
-	}
-	return err
+	return nil
 }
diff --git a/router/router.go b/router/router.go
index 5a4e5027..fb36238b 100644
--- a/router/router.go
+++ b/router/router.go
@@ -42,10 +42,10 @@ import (
 // NewJsonDirectoryServer is used to serve .json files from a directory as a single blob. For example,
 // given a directory containing the files "a.json" and "b.json", this returns a Handle which serves JSON like:
 //
-// {
-//   "a": { ... content from the file a.json ... },
-//   "b": { ... content from the file b.json ... }
-// }
+//	{
+//	  "a": { ... content from the file a.json ... },
+//	  "b": { ... content from the file b.json ... }
+//	}
 //
 // This function stores the file contents in memory, and should not be used on large directories.
 // If the root directory, or any of the files in it, cannot be read, then the program will exit.
diff --git a/static/bidder-info/adnuntius.yaml b/static/bidder-info/adnuntius.yaml
index 22cb1c77..0e1d86dd 100644
--- a/static/bidder-info/adnuntius.yaml
+++ b/static/bidder-info/adnuntius.yaml
@@ -1,4 +1,5 @@
 endpoint: "https://ads.adnuntius.delivery/i"
+extra_info: "https://europe.delivery.adnuntius.com/i"
 maintainer:
   email: hello@adnuntius.com
 gvlVendorID: 855
diff --git a/static/bidder-info/andbeyondmedia.yaml b/static/bidder-info/beyondmedia.yaml
similarity index 100%
rename from static/bidder-info/andbeyondmedia.yaml
rename to static/bidder-info/beyondmedia.yaml
diff --git a/static/bidder-info/bidstack.yaml b/static/bidder-info/bidstack.yaml
new file mode 100644
index 00000000..894d4fe2
--- /dev/null
+++ b/static/bidder-info/bidstack.yaml
@@ -0,0 +1,10 @@
+endpoint: "https://server.bidstack.com/openrtb"
+maintainer:
+  email: tech@bidstack.com
+endpointCompression: gzip
+gvlVendorID: 462
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - video
diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml
index 21f59e9d..b3de838c 100644
--- a/static/bidder-info/rubicon.yaml
+++ b/static/bidder-info/rubicon.yaml
@@ -8,10 +8,12 @@ capabilities:
     mediaTypes:
       - banner
       - video
+      - native
   site:
     mediaTypes:
       - banner
       - video
+      - native
 userSync:
   # rubicon supports user syncing, but requires configuration by the host. contact this
   # bidder directly at the email address in this file to ask about enabling user sync.
diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml
index b9e9445e..1014e5a1 100644
--- a/static/bidder-info/smaato.yaml
+++ b/static/bidder-info/smaato.yaml
@@ -7,10 +7,12 @@ capabilities:
     mediaTypes:
       - banner
       - video
+      - native
   site:
     mediaTypes:
       - banner
       - video
+      - native
 userSync:
   redirect:
     url: "https://s.ad.smaato.net/c/?adExInit=p&redir={{.RedirectURL}}"
diff --git a/static/bidder-params/andbeyondmedia.json b/static/bidder-params/beyondmedia.json
similarity index 100%
rename from static/bidder-params/andbeyondmedia.json
rename to static/bidder-params/beyondmedia.json
diff --git a/static/bidder-params/bidstack.json b/static/bidder-params/bidstack.json
new file mode 100644
index 00000000..6fd0f6b5
--- /dev/null
+++ b/static/bidder-params/bidstack.json
@@ -0,0 +1,15 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Bidstack Adapter Params",
+  "description": "A schema which validates params accepted by the Bidstack adapter",
+  "type": "object",
+  "properties": {
+    "publisherId": {
+      "type": "string",
+      "format": "uuid",
+      "minLength": 1,
+      "description": "An ID which identifies the publisher in Bidstack system"
+    }
+  },
+  "required" : [ "publisherId" ]
+}
diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go
index 70fcf51f..ed71ded4 100644
--- a/stored_requests/backends/http_fetcher/fetcher.go
+++ b/stored_requests/backends/http_fetcher/fetcher.go
@@ -28,26 +28,26 @@ import (
 //
 // The above endpoints should return a payload like:
 //
-// {
-//   "requests": {
-//     "req1": { ... stored data for req1 ... },
-//     "req2": { ... stored data for req2 ... },
-//   },
-//   "imps": {
-//     "imp1": { ... stored data for imp1 ... },
-//     "imp2": { ... stored data for imp2 ... },
-//     "imp3": null // If imp3 is not found
-//   }
-// }
-// or
-// {
-//   "accounts": {
-//     "acc1": { ... config data for acc1 ... },
-//     "acc2": { ... config data for acc2 ... },
-//   },
-// }
+//	{
+//	  "requests": {
+//	    "req1": { ... stored data for req1 ... },
+//	    "req2": { ... stored data for req2 ... },
+//	  },
+//	  "imps": {
+//	    "imp1": { ... stored data for imp1 ... },
+//	    "imp2": { ... stored data for imp2 ... },
+//	    "imp3": null // If imp3 is not found
+//	  }
+//	}
 //
+// or
 //
+//	{
+//	  "accounts": {
+//	    "acc1": { ... config data for acc1 ... },
+//	    "acc2": { ... config data for acc2 ... },
+//	  },
+//	}
 func NewFetcher(client *http.Client, endpoint string) *HttpFetcher {
 	// Do some work up-front to figure out if the (configurable) endpoint has a query string or not.
 	// When we build requests, we'll either want to add `?request-ids=...&imp-ids=...` _or_
@@ -106,9 +106,11 @@ func (fetcher *HttpFetcher) FetchResponses(ctx context.Context, ids []string) (d
 // GET {endpoint}?account-ids=["account1","account2",...]
 //
 // The endpoint is expected to respond with a JSON map with accountID -> json.RawMessage
-// {
-//   "account1": { ... account json ... }
-// }
+//
+//	{
+//	  "account1": { ... account json ... }
+//	}
+//
 // The JSON contents of account config is returned as-is (NOT validated)
 func (fetcher *HttpFetcher) FetchAccounts(ctx context.Context, accountIDs []string) (map[string]json.RawMessage, []error) {
 	if len(accountIDs) == 0 {
diff --git a/stored_requests/events/http/http.go b/stored_requests/events/http/http.go
index 6450e0de..084d89f9 100644
--- a/stored_requests/events/http/http.go
+++ b/stored_requests/events/http/http.go
@@ -23,40 +23,44 @@ import (
 // It expects the following endpoint to exist remotely:
 //
 // GET {endpoint}
-//   -- Returns all the known Stored Requests and Stored Imps.
+//
+//	-- Returns all the known Stored Requests and Stored Imps.
+//
 // GET {endpoint}?last-modified={timestamp}
-//   -- Returns the Stored Requests and Stored Imps which have been updated since the last timestamp.
-//      This timestamp will be sent in the rfc3339 format, using UTC and no timezone shift.
-//      For more info, see: https://tools.ietf.org/html/rfc3339
+//
+//	-- Returns the Stored Requests and Stored Imps which have been updated since the last timestamp.
+//	   This timestamp will be sent in the rfc3339 format, using UTC and no timezone shift.
+//	   For more info, see: https://tools.ietf.org/html/rfc3339
 //
 // The responses should be JSON like this:
 //
-// {
-//   "requests": {
-//     "request1": { ... stored request data ... },
-//     "request2": { ... stored request data ... },
-//     "request3": { ... stored request data ... },
-//   },
-//   "imps": {
-//     "imp1": { ... stored data for imp1 ... },
-//     "imp2": { ... stored data for imp2 ... },
-//   },
-//   "responses": {
-//     "resp1": { ... stored data for resp1 ... },
-//     "resp2": { ... stored data for resp2 ... },
-//   }
-// }
+//	{
+//	  "requests": {
+//	    "request1": { ... stored request data ... },
+//	    "request2": { ... stored request data ... },
+//	    "request3": { ... stored request data ... },
+//	  },
+//	  "imps": {
+//	    "imp1": { ... stored data for imp1 ... },
+//	    "imp2": { ... stored data for imp2 ... },
+//	  },
+//	  "responses": {
+//	    "resp1": { ... stored data for resp1 ... },
+//	    "resp2": { ... stored data for resp2 ... },
+//	  }
+//	}
+//
 // or
-// {
-//   "accounts": {
-//     "acc1": { ... config data for acc1 ... },
-//     "acc2": { ... config data for acc2 ... },
-//   },
-// }
+//
+//	{
+//	  "accounts": {
+//	    "acc1": { ... config data for acc1 ... },
+//	    "acc2": { ... config data for acc2 ... },
+//	  },
+//	}
 //
 // To signal deletions, the endpoint may return { "deleted": true }
 // in place of the Stored Data if the "last-modified" param existed.
-//
 func NewHTTPEvents(client *httpCore.Client, endpoint string, ctxProducer func() (ctx context.Context, canceller func()), refreshRate time.Duration) *HTTPEvents {
 	// If we're not given a function to produce Contexts, use the Background one.
 	if ctxProducer == nil {
diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go
index d8716297..3b468731 100644
--- a/util/jsonutil/jsonutil.go
+++ b/util/jsonutil/jsonutil.go
@@ -13,7 +13,7 @@ var openCurlyBracket = []byte("{")[0]
 var closingCurlyBracket = []byte("}")[0]
 var quote = []byte(`"`)[0]
 
-//Finds element in json byte array with any level of nesting
+// Finds element in json byte array with any level of nesting
 func FindElement(extension []byte, elementNames ...string) (bool, int64, int64, error) {
 	elementName := elementNames[0]
 	buf := bytes.NewBuffer(extension)
@@ -96,7 +96,7 @@ func FindElement(extension []byte, elementNames ...string) (bool, int64, int64,
 	return found, startIndex, endIndex, nil
 }
 
-//Drops element from json byte array
+// Drops element from json byte array
 // - Doesn't support drop element from json list
 // - Keys in the path can skip levels
 // - First found element will be removed
diff --git a/version/version.go b/version/version.go
index 4292abbe..55705b2a 100644
--- a/version/version.go
+++ b/version/version.go
@@ -2,7 +2,9 @@ package version
 
 // Ver holds the version derived from the latest git tag
 // Populated using:
-//    go build -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//`"
+//
+//	go build -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//`"
+//
 // Populated automatically at build / releases in the Docker image
 var Ver string
 
@@ -11,7 +13,9 @@ const VerUnknown = "unknown"
 
 // Rev holds binary revision string
 // Populated using:
-//    go build -ldflags "-X github.com/prebid/prebid-server/version.Rev=`git rev-parse --short HEAD`"
+//
+//	go build -ldflags "-X github.com/prebid/prebid-server/version.Rev=`git rev-parse --short HEAD`"
+//
 // Populated automatically at build / releases in the Docker image
 // See issue #559
 var Rev string


Test patch:
diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go
index 734a4281..27e006e1 100644
--- a/adapters/adnuntius/adnuntius_test.go
+++ b/adapters/adnuntius/adnuntius_test.go
@@ -19,7 +19,7 @@ func TestJsonSamples(t *testing.T) {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
 	}
 	assertTzo(t, bidder)
-	replaceRealTimeWithKnownTime(bidder)
+	AssignDefaultValues(bidder)
 
 	adapterstest.RunJSONBidderTest(t, "adnuntiustest", bidder)
 }
@@ -38,9 +38,10 @@ func (ft *FakeTime) Now() time.Time {
 	return ft.time
 }
 
-func replaceRealTimeWithKnownTime(bidder adapters.Bidder) {
+func AssignDefaultValues(bidder adapters.Bidder) {
 	bidderAdnuntius, _ := bidder.(*adapter)
 	bidderAdnuntius.time = &FakeTime{
 		time: time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC),
 	}
+	bidderAdnuntius.extraInfo = "http://gdpr.url"
 }
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json
index 578722bd..c73d69ba 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json
@@ -38,7 +38,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0",
+				"uri": "http://gdpr.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json
index ea629fe6..bcfecfa8 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json
@@ -4,7 +4,6 @@
 		"user": {
 			"id": "1kjh3429kjh295jkl",
 			"ext": {
-				"consent": "CONSENT_STRING"
 			}
 		},
 		"device": {
diff --git a/adapters/andbeyondmedia/andbeyondmedia_test.go b/adapters/beyondmedia/beyondmedia_test.go
similarity index 72%
rename from adapters/andbeyondmedia/andbeyondmedia_test.go
rename to adapters/beyondmedia/beyondmedia_test.go
index 8b50aec8..9b22d1bb 100644
--- a/adapters/andbeyondmedia/andbeyondmedia_test.go
+++ b/adapters/beyondmedia/beyondmedia_test.go
@@ -1,4 +1,4 @@
-package andbeyondmedia
+package beyondmedia
 
 import (
 	"testing"
@@ -9,12 +9,12 @@ import (
 )
 
 func TestJsonSamples(t *testing.T) {
-	bidder, buildErr := Builder(openrtb_ext.BidderAndBeyondMedia, config.Adapter{
+	bidder, buildErr := Builder(openrtb_ext.BidderBeyondMedia, config.Adapter{
 		Endpoint: "http://backend.andbeyond.media/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
 	}
 
-	adapterstest.RunJSONBidderTest(t, "andbeyondmediatest", bidder)
+	adapterstest.RunJSONBidderTest(t, "beyondmediatest", bidder)
 }
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-banner.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.json
similarity index 98%
rename from adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-banner.json
rename to adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.json
index 136fc666..d53a5f1b 100644
--- a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-banner.json
+++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-banner.json
@@ -97,7 +97,7 @@
                                     }
                                 }
                             ],
-                            "seat": "andbeyondmedia"
+                            "seat": "beyondmedia"
                         }
                     ],
                     "cur": "USD"
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-native.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-native.json
similarity index 98%
rename from adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-native.json
rename to adapters/beyondmedia/beyondmediatest/exemplary/simple-native.json
index 95f7f335..f0a62312 100644
--- a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-native.json
+++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-native.json
@@ -81,7 +81,7 @@
                                     }
                                 }
                             ],
-                            "seat": "andbeyondmedia"
+                            "seat": "beyondmedia"
                         }
                     ],
                     "cur": "USD"
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-video.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-video.json
similarity index 99%
rename from adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-video.json
rename to adapters/beyondmedia/beyondmediatest/exemplary/simple-video.json
index e11129f4..0b650d62 100644
--- a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-video.json
+++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-video.json
@@ -93,7 +93,7 @@
                                     }
                                 }
                             ],
-                            "seat": "andbeyondmedia"
+                            "seat": "beyondmedia"
                         }
                     ],
                     "cur": "USD"
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-web-banner.json b/adapters/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json
similarity index 98%
rename from adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-web-banner.json
rename to adapters/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json
index cdd905c5..0e04a25d 100644
--- a/adapters/andbeyondmedia/andbeyondmediatest/exemplary/simple-web-banner.json
+++ b/adapters/beyondmedia/beyondmediatest/exemplary/simple-web-banner.json
@@ -97,7 +97,7 @@
                                     }
                                 }
                             ],
-                            "seat": "andbeyondmedia"
+                            "seat": "beyondmedia"
                         }
                     ],
                     "cur": "USD"
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_media_type.json b/adapters/beyondmedia/beyondmediatest/supplemental/bad_media_type.json
similarity index 98%
rename from adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_media_type.json
rename to adapters/beyondmedia/beyondmediatest/supplemental/bad_media_type.json
index e61b19b7..542529ab 100644
--- a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_media_type.json
+++ b/adapters/beyondmedia/beyondmediatest/supplemental/bad_media_type.json
@@ -70,7 +70,7 @@
                                 }
                             }
                         ],
-                        "seat": "andbeyondmedia"
+                        "seat": "beyondmedia"
                     }
                 ],
                 "cur": "USD"
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_response.json b/adapters/beyondmedia/beyondmediatest/supplemental/bad_response.json
similarity index 100%
rename from adapters/andbeyondmedia/andbeyondmediatest/supplemental/bad_response.json
rename to adapters/beyondmedia/beyondmediatest/supplemental/bad_response.json
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-204.json b/adapters/beyondmedia/beyondmediatest/supplemental/status-204.json
similarity index 100%
rename from adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-204.json
rename to adapters/beyondmedia/beyondmediatest/supplemental/status-204.json
diff --git a/adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-not-200.json b/adapters/beyondmedia/beyondmediatest/supplemental/status-not-200.json
similarity index 100%
rename from adapters/andbeyondmedia/andbeyondmediatest/supplemental/status-not-200.json
rename to adapters/beyondmedia/beyondmediatest/supplemental/status-not-200.json
diff --git a/adapters/andbeyondmedia/params_test.go b/adapters/beyondmedia/params_test.go
similarity index 79%
rename from adapters/andbeyondmedia/params_test.go
rename to adapters/beyondmedia/params_test.go
index 6a09fdb6..9b0f8e8f 100644
--- a/adapters/andbeyondmedia/params_test.go
+++ b/adapters/beyondmedia/params_test.go
@@ -1,4 +1,4 @@
-package andbeyondmedia
+package beyondmedia
 
 import (
 	"encoding/json"
@@ -14,7 +14,7 @@ func TestValidParams(t *testing.T) {
 	}
 
 	for _, p := range validParams {
-		if err := validator.Validate(openrtb_ext.BidderAndBeyondMedia, json.RawMessage(p)); err != nil {
+		if err := validator.Validate(openrtb_ext.BidderBeyondMedia, json.RawMessage(p)); err != nil {
 			t.Errorf("Schema rejected valid params: %s", p)
 		}
 	}
@@ -27,7 +27,7 @@ func TestInvalidParams(t *testing.T) {
 	}
 
 	for _, p := range invalidParams {
-		if err := validator.Validate(openrtb_ext.BidderAndBeyondMedia, json.RawMessage(p)); err == nil {
+		if err := validator.Validate(openrtb_ext.BidderBeyondMedia, json.RawMessage(p)); err == nil {
 			t.Errorf("Schema allowed invalid params: %s", p)
 		}
 	}
diff --git a/adapters/bidstack/bidstack_test.go b/adapters/bidstack/bidstack_test.go
new file mode 100644
index 00000000..38ed711c
--- /dev/null
+++ b/adapters/bidstack/bidstack_test.go
@@ -0,0 +1,50 @@
+package bidstack
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/stretchr/testify/assert"
+
+	"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) {
+	bidder, buildErr := Builder(openrtb_ext.BidderBidstack, config.Adapter{Endpoint: "http://mock-adserver.url"}, config.Server{})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "bidstacktest", bidder)
+}
+
+func TestPrepareHeaders(t *testing.T) {
+	publisherID := "12345"
+	expected := http.Header{
+		"Content-Type":  {"application/json"},
+		"Authorization": {"Bearer " + publisherID},
+	}
+
+	actual, err := prepareHeaders(&openrtb2.BidRequest{Imp: []openrtb2.Imp{
+		{Ext: []byte(`{"bidder":{"publisherId":"` + publisherID + `"}}`)}},
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, expected, actual)
+}
+
+func TestGetBidderExt(t *testing.T) {
+	publisherID := "12345"
+	expected := openrtb_ext.ImpExtBidstack{PublisherID: publisherID}
+
+	actual, err := getBidderExt(openrtb2.Imp{
+		Ext: []byte(`{"bidder":{"publisherId":"` + publisherID + `"}}`),
+	})
+
+	assert.NoError(t, err)
+	assert.Equal(t, expected, actual)
+}
diff --git a/adapters/bidstack/bidstacktest/exemplary/simple-app-video.json b/adapters/bidstack/bidstacktest/exemplary/simple-app-video.json
new file mode 100644
index 00000000..46729f9b
--- /dev/null
+++ b/adapters/bidstack/bidstacktest/exemplary/simple-app-video.json
@@ -0,0 +1,143 @@
+{
+  "mockBidRequest": {
+    "id": "bid-request-id",
+    "device": {
+      "ip": "0.0.0.0",
+      "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+    },
+    "app": {
+      "id": "app-id",
+      "bundle": "com.app.id"
+    },
+    "imp": [
+      {
+        "id": "imp-id-1",
+        "bidfloor": 4.20,
+        "bidfloorcur": "GBP",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "protocols": [
+            2,
+            5
+          ],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "GBP": {
+              "USD": 0.5
+            }
+          },
+          "usepbsrates": false
+        }
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://mock-adserver.url",
+        "body": {
+          "id": "bid-request-id",
+          "device": {
+            "ip": "0.0.0.0",
+            "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+          },
+          "app": {
+            "id": "app-id",
+            "bundle": "com.app.id"
+          },
+          "imp": [
+            {
+              "id": "imp-id-1",
+              "bidfloor": 2.10,
+              "bidfloorcur": "USD",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "protocols": [
+                  2,
+                  5
+                ],
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "bidder": {
+                  "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+                }
+              }
+            }
+          ],
+          "ext": {
+            "prebid": {
+              "currency": {
+                "rates": {
+                  "GBP": {
+                    "USD": 0.5
+                  }
+                },
+                "usepbsrates": false
+              }
+            }
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "bid-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "bid_id-1",
+                  "impid": "imp-id-1",
+                  "price": 0.27543,
+                  "adm": "<VAST version=\"3.0\"></VAST>",
+                  "cid": "cid",
+                  "crid": "crid",
+                  "dealid": "dealid"
+                }
+              ],
+              "seat": "seat"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "bid_id-1",
+            "impid": "imp-id-1",
+            "price": 0.27543,
+            "adm": "<VAST version=\"3.0\"></VAST>",
+            "cid": "cid",
+            "crid": "crid",
+            "dealid": "dealid"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json b/adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json
new file mode 100644
index 00000000..b8160c3e
--- /dev/null
+++ b/adapters/bidstack/bidstacktest/supplemental/currency_rate_not_found.json
@@ -0,0 +1,54 @@
+{
+  "mockBidRequest": {
+    "id": "bid-request-id",
+    "device": {
+      "ip": "0.0.0.0",
+      "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+    },
+    "app": {
+      "id": "app-id",
+      "bundle": "com.app.id"
+    },
+    "imp": [
+      {
+        "id": "imp-id-1",
+        "bidfloor": 4.20,
+        "bidfloorcur": "JPY",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "protocols": [
+            2,
+            5
+          ],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "MXN": {
+              "USD": 0.05
+            }
+          },
+          "usepbsrates": false
+        }
+      }
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Currency conversion rate not found: 'JPY' => 'USD'",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidstack/bidstacktest/supplemental/status-204.json b/adapters/bidstack/bidstacktest/supplemental/status-204.json
new file mode 100644
index 00000000..2c84a354
--- /dev/null
+++ b/adapters/bidstack/bidstacktest/supplemental/status-204.json
@@ -0,0 +1,78 @@
+{
+  "mockBidRequest": {
+    "id": "bid-request-id",
+    "device": {
+      "ip": "0.0.0.0",
+      "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+    },
+    "app": {
+      "id": "app-id",
+      "bundle": "com.app.id"
+    },
+    "imp": [
+      {
+        "id": "imp-id-1",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "protocols": [
+            2,
+            5
+          ],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://mock-adserver.url",
+        "body": {
+          "id": "bid-request-id",
+          "device": {
+            "ip": "0.0.0.0",
+            "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+          },
+          "app": {
+            "id": "app-id",
+            "bundle": "com.app.id"
+          },
+          "imp": [
+            {
+              "id": "imp-id-1",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "protocols": [
+                  2,
+                  5
+                ],
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "bidder": {
+                  "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": {}
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
diff --git a/adapters/bidstack/bidstacktest/supplemental/status-400.json b/adapters/bidstack/bidstacktest/supplemental/status-400.json
new file mode 100644
index 00000000..eaf5b0e5
--- /dev/null
+++ b/adapters/bidstack/bidstacktest/supplemental/status-400.json
@@ -0,0 +1,83 @@
+{
+  "mockBidRequest": {
+    "id": "bid-request-id",
+    "device": {
+      "ip": "0.0.0.0",
+      "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+    },
+    "app": {
+      "id": "app-id",
+      "bundle": "com.app.id"
+    },
+    "imp": [
+      {
+        "id": "imp-id-1",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "protocols": [
+            2,
+            5
+          ],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://mock-adserver.url",
+        "body": {
+          "id": "bid-request-id",
+          "device": {
+            "ip": "0.0.0.0",
+            "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+          },
+          "app": {
+            "id": "app-id",
+            "bundle": "com.app.id"
+          },
+          "imp": [
+            {
+              "id": "imp-id-1",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "protocols": [
+                  2,
+                  5
+                ],
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "bidder": {
+                  "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 400,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "bad request from publisher",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidstack/bidstacktest/supplemental/status-404.json b/adapters/bidstack/bidstacktest/supplemental/status-404.json
new file mode 100644
index 00000000..7bed7485
--- /dev/null
+++ b/adapters/bidstack/bidstacktest/supplemental/status-404.json
@@ -0,0 +1,83 @@
+{
+  "mockBidRequest": {
+    "id": "bid-request-id",
+    "device": {
+      "ip": "0.0.0.0",
+      "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+    },
+    "app": {
+      "id": "app-id",
+      "bundle": "com.app.id"
+    },
+    "imp": [
+      {
+        "id": "imp-id-1",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "protocols": [
+            2,
+            5
+          ],
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "bidder": {
+            "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://mock-adserver.url",
+        "body": {
+          "id": "bid-request-id",
+          "device": {
+            "ip": "0.0.0.0",
+            "ifa": "ce76100d-82cc-4491-a6c9-50e0e539454c"
+          },
+          "app": {
+            "id": "app-id",
+            "bundle": "com.app.id"
+          },
+          "imp": [
+            {
+              "id": "imp-id-1",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "protocols": [
+                  2,
+                  5
+                ],
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "bidder": {
+                  "publisherId": "225a4f40-1df1-48b2-b238-8774a638ce6e"
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 404,
+        "body": {}
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unexpected response status code: 404",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidstack/params_test.go b/adapters/bidstack/params_test.go
new file mode 100644
index 00000000..90150f47
--- /dev/null
+++ b/adapters/bidstack/params_test.go
@@ -0,0 +1,43 @@
+package bidstack
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+	`{"publisherId": "355cf800-8348-433a-9d95-70345fa70afc"}`,
+}
+
+var invalidParams = []string{
+	`{"publisherId": ""}`,
+	`{"publisherId": "non-uuid"}`,
+}
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderBidstack, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderBidstack, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go
index a485527e..f33b0531 100644
--- a/adapters/rubicon/rubicon_test.go
+++ b/adapters/rubicon/rubicon_test.go
@@ -3,6 +3,7 @@ package rubicon
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"net/http"
 	"strconv"
 	"testing"
@@ -50,208 +51,89 @@ type rubiBidInfo struct {
 
 var rubidata rubiBidInfo
 
-func getTestSizes() map[int]openrtb2.Format {
-	return map[int]openrtb2.Format{
-		15: {W: 300, H: 250},
-		10: {W: 300, H: 600},
-		2:  {W: 728, H: 91},
-		9:  {W: 160, H: 600},
-		8:  {W: 120, H: 600},
-		33: {W: 180, H: 500},
-		43: {W: 320, H: 50},
-	}
-}
-
-func TestParseSizes(t *testing.T) {
-	SIZE_ID := getTestSizes()
-
-	sizes := []openrtb2.Format{
-		SIZE_ID[10],
-		SIZE_ID[15],
-	}
-	primary, alt, err := parseRubiconSizes(sizes)
-	assert.Nil(t, err, "Parsing error: %v", err)
-	assert.Equal(t, 15, primary, "Primary %d != 15", primary)
-	assert.Equal(t, 1, len(alt), "Alt not len 1")
-	assert.Equal(t, 10, alt[0], "Alt not 10: %d", alt[0])
-
-	sizes = []openrtb2.Format{
+func TestAppendTracker(t *testing.T) {
+	testScenarios := []rubiAppendTrackerUrlTestScenario{
 		{
-			W: 1111,
-			H: 2222,
+			source:   "http://test.url/",
+			tracker:  "prebid",
+			expected: "http://test.url/?tk_xint=prebid",
 		},
-		SIZE_ID[15],
-	}
-	primary, alt, err = parseRubiconSizes(sizes)
-	assert.Nil(t, err, "Shouldn't have thrown error for invalid size 1111x1111 since we still have a valid one")
-	assert.Equal(t, 15, primary, "Primary %d != 15", primary)
-	assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt))
-
-	sizes = []openrtb2.Format{
-		SIZE_ID[15],
-	}
-	primary, alt, err = parseRubiconSizes(sizes)
-	assert.Nil(t, err, "Parsing error: %v", err)
-	assert.Equal(t, 15, primary, "Primary %d != 15", primary)
-	assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt))
-
-	sizes = []openrtb2.Format{
 		{
-			W: 1111,
-			H: 1222,
+			source:   "http://test.url/?hello=true",
+			tracker:  "prebid",
+			expected: "http://test.url/?hello=true&tk_xint=prebid",
 		},
 	}
-	primary, alt, err = parseRubiconSizes(sizes)
-	assert.NotNil(t, err, "Parsing error: %v", err)
-	assert.Equal(t, 0, primary, "Primary %d != 15", primary)
-	assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt))
-}
 
-func TestMASAlgorithm(t *testing.T) {
-	SIZE_ID := getTestSizes()
-	type output struct {
-		primary int
-		alt     []int
-		ok      bool
-	}
-	type testStub struct {
-		input  []openrtb2.Format
-		output output
+	for _, scenario := range testScenarios {
+		res := appendTrackerToUrl(scenario.source, scenario.tracker)
+		assert.Equal(t, scenario.expected, res, "Failed to convert '%s' to '%s'", res, scenario.expected)
 	}
+}
 
-	testStubs := []testStub{
-		{
-			[]openrtb2.Format{
-				SIZE_ID[2],
-				SIZE_ID[9],
-			},
-			output{2, []int{9}, false},
-		},
-		{
-			[]openrtb2.Format{
-
-				SIZE_ID[9],
-				SIZE_ID[15],
-			},
-			output{15, []int{9}, false},
-		},
-		{
-			[]openrtb2.Format{
-				SIZE_ID[2],
-				SIZE_ID[15],
-			},
-			output{15, []int{2}, false},
-		},
-		{
-			[]openrtb2.Format{
-				SIZE_ID[15],
-				SIZE_ID[9],
-				SIZE_ID[2],
-			},
-			output{15, []int{2, 9}, false},
-		},
-		{
-			[]openrtb2.Format{
-				SIZE_ID[10],
-				SIZE_ID[9],
-				SIZE_ID[2],
-			},
-			output{2, []int{10, 9}, false},
-		},
-		{
-			[]openrtb2.Format{
-				SIZE_ID[33],
-				SIZE_ID[8],
-				SIZE_ID[15],
-			},
-			output{15, []int{33, 8}, false},
-		},
+func TestSetImpNative(t *testing.T) {
+	testScenarios := []struct {
+		request       string
+		impNative     map[string]interface{}
+		expectedError error
+	}{
 		{
-			[]openrtb2.Format{
-				SIZE_ID[33],
-				SIZE_ID[8],
-				SIZE_ID[9],
-				SIZE_ID[2],
-			},
-			output{2, []int{33, 8, 9}, false},
+			request:       "{}",
+			impNative:     map[string]interface{}{"somekey": "someValue"},
+			expectedError: fmt.Errorf("unable to find imp in json data"),
 		},
 		{
-			[]openrtb2.Format{
-				SIZE_ID[33],
-				SIZE_ID[8],
-				SIZE_ID[9],
-			},
-			output{9, []int{33, 8}, false},
+			request:       "{\"imp\":[]}",
+			impNative:     map[string]interface{}{"somekey": "someValue"},
+			expectedError: fmt.Errorf("unable to find imp[0] in json data"),
 		},
 		{
-			[]openrtb2.Format{
-				SIZE_ID[33],
-				SIZE_ID[8],
-				SIZE_ID[2],
-			},
-			output{2, []int{33, 8}, false},
-		},
-		{
-			[]openrtb2.Format{
-				SIZE_ID[33],
-				SIZE_ID[2],
-			},
-			output{2, []int{33}, false},
+			request:       "{\"imp\":[{}]}",
+			impNative:     map[string]interface{}{"somekey": "someValue"},
+			expectedError: fmt.Errorf("unable to find imp[0].native in json data"),
 		},
+	}
+	for _, scenario := range testScenarios {
+		_, err := setImpNative([]byte(scenario.request), scenario.impNative)
+		assert.Equal(t, scenario.expectedError, err)
+	}
+}
+
+func TestResolveNativeObject(t *testing.T) {
+	testScenarios := []struct {
+		nativeObject  openrtb2.Native
+		target        map[string]interface{}
+		expectedError error
+	}{
 		{
-			[]openrtb2.Format{
-				SIZE_ID[8],
-			},
-			output{8, []int{}, false},
+			nativeObject:  openrtb2.Native{Ver: "1.0", Request: "{\"eventtrackers\": \"someWrongValue\"}"},
+			target:        map[string]interface{}{},
+			expectedError: nil,
 		},
 		{
-			[]openrtb2.Format{},
-			output{0, []int{}, true},
+			nativeObject:  openrtb2.Native{Ver: "1.1", Request: "{\"eventtrackers\": \"someWrongValue\"}"},
+			target:        map[string]interface{}{},
+			expectedError: nil,
 		},
 		{
-			[]openrtb2.Format{
-				{W: 1111,
-					H: 2345,
-				},
-			},
-			output{0, []int{}, true},
+			nativeObject:  openrtb2.Native{Ver: "1", Request: "{\"eventtrackers\": \"someWrongValue\"}"},
+			target:        map[string]interface{}{},
+			expectedError: fmt.Errorf("Eventtrackers are not present or not of array type"),
 		},
-	}
-
-	for _, test := range testStubs {
-		prim, alt, err := parseRubiconSizes(test.input)
-
-		assert.Equal(t, test.output.primary, prim,
-			"Error in parsing rubicon sizes: MAS algorithm fail at primary: testcase %v", test.input)
-
-		assert.Equal(t, len(test.output.alt), len(alt),
-			"Error in parsing rubicon sizes: MAS Algorithm fail at alt: testcase %v", test.input)
-
-		assert.False(t, err != nil && !test.output.ok,
-			"Error in parsing rubicon sizes: MAS Algorithm fail at throwing error: testcase %v", test.input)
-
-		assert.False(t, err == nil && test.output.ok,
-			"Error in parsing rubicon sizes: MAS Algorithm fail at throwing error: testcase %v", test.input)
-	}
-}
-
-func TestAppendTracker(t *testing.T) {
-	testScenarios := []rubiAppendTrackerUrlTestScenario{
 		{
-			source:   "http://test.url/",
-			tracker:  "prebid",
-			expected: "http://test.url/?tk_xint=prebid",
+			nativeObject:  openrtb2.Native{Ver: "1", Request: "{\"eventtrackers\": [], \"context\": \"someWrongValue\"}"},
+			target:        map[string]interface{}{},
+			expectedError: fmt.Errorf("Context is not present or not of int type"),
 		},
 		{
-			source:   "http://test.url/?hello=true",
-			tracker:  "prebid",
-			expected: "http://test.url/?hello=true&tk_xint=prebid",
+			nativeObject:  openrtb2.Native{Ver: "1", Request: "{\"eventtrackers\": [], \"context\": 1}"},
+			target:        map[string]interface{}{},
+			expectedError: fmt.Errorf("Plcmttype is not present or not of int type"),
 		},
 	}
-
 	for _, scenario := range testScenarios {
-		res := appendTrackerToUrl(scenario.source, scenario.tracker)
-		assert.Equal(t, scenario.expected, res, "Failed to convert '%s' to '%s'", res, scenario.expected)
+		_, err := resolveNativeObject(&scenario.nativeObject, scenario.target)
+		assert.Equal(t, scenario.expectedError, err)
 	}
 }
 
@@ -362,7 +244,6 @@ func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) {
 			CurrencyConversions: mockConversions,
 		}
 
-		SIZE_ID := getTestSizes()
 		bidder := new(RubiconAdapter)
 
 		request := &openrtb2.BidRequest{
@@ -373,8 +254,7 @@ func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) {
 				BidFloor:    scenario.bidFloor,
 				Banner: &openrtb2.Banner{
 					Format: []openrtb2.Format{
-						SIZE_ID[15],
-						SIZE_ID[10],
+						{W: 300, H: 250},
 					},
 				},
 				Ext: json.RawMessage(`{"bidder": {
@@ -421,7 +301,6 @@ func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 {
 }
 
 func TestOpenRTBRequest(t *testing.T) {
-	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
 
 	rubidata = rubiBidInfo{
@@ -439,8 +318,8 @@ func TestOpenRTBRequest(t *testing.T) {
 			ID: "test-imp-banner-id",
 			Banner: &openrtb2.Banner{
 				Format: []openrtb2.Format{
-					SIZE_ID[15],
-					SIZE_ID[10],
+					{W: 300, H: 250},
+					{W: 300, H: 600},
 				},
 			},
 			Ext: json.RawMessage(`{"bidder": {
@@ -564,7 +443,6 @@ func TestOpenRTBRequest(t *testing.T) {
 }
 
 func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) {
-	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
 
 	request := &openrtb2.BidRequest{
@@ -573,8 +451,7 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) {
 			ID: "test-imp-id",
 			Banner: &openrtb2.Banner{
 				Format: []openrtb2.Format{
-					SIZE_ID[15],
-					SIZE_ID[10],
+					{W: 300, H: 250},
 				},
 			},
 			Video: &openrtb2.Video{
@@ -615,7 +492,6 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) {
 }
 
 func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) {
-	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
 
 	request := &openrtb2.BidRequest{
@@ -624,8 +500,7 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) {
 			ID: "test-imp-id",
 			Banner: &openrtb2.Banner{
 				Format: []openrtb2.Format{
-					SIZE_ID[15],
-					SIZE_ID[10],
+					{W: 300, H: 250},
 				},
 			},
 			Ext: json.RawMessage(`{
@@ -720,7 +595,6 @@ func TestOpenRTBFirstPartyDataPopulating(t *testing.T) {
 }
 
 func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) {
-	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
 
 	badvOverflowed := make([]string, 100)
@@ -735,7 +609,7 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) {
 			ID: "test-imp-id",
 			Banner: &openrtb2.Banner{
 				Format: []openrtb2.Format{
-					SIZE_ID[15],
+					{W: 300, H: 250},
 				},
 			},
 			Ext: json.RawMessage(`{
@@ -766,7 +640,6 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) {
 }
 
 func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
-	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
 
 	request := &openrtb2.BidRequest{
@@ -775,8 +648,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 			ID: "test-imp-id",
 			Banner: &openrtb2.Banner{
 				Format: []openrtb2.Format{
-					SIZE_ID[15],
-					SIZE_ID[10],
+					{W: 300, H: 250},
 				},
 			},
 			Ext: json.RawMessage(`{"bidder": {
@@ -845,15 +717,6 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 	assert.NotNil(t, userExt.Eids)
 	assert.Equal(t, 4, len(userExt.Eids), "Eids values are not as expected!")
 
-	assert.NotNil(t, userExt.TpID)
-	assert.Equal(t, 2, len(userExt.TpID), "TpID values are not as expected!")
-
-	// adserver.org
-	assert.Equal(t, "tdid", userExt.TpID[0].Source, "TpID source value is not as expected!")
-
-	// liveintent.com
-	assert.Equal(t, "liveintent.com", userExt.TpID[1].Source, "TpID source value is not as expected!")
-
 	// liveramp.com
 	assert.Equal(t, "LIVERAMPID", userExt.LiverampIdl, "Liveramp_idl value is not as expected!")
 
@@ -868,7 +731,6 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) {
 }
 
 func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t *testing.T) {
-	SIZE_ID := getTestSizes()
 	bidder := new(RubiconAdapter)
 
 	request := &openrtb2.BidRequest{
@@ -877,8 +739,7 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t
 			ID: "test-imp-id",
 			Banner: &openrtb2.Banner{
 				Format: []openrtb2.Format{
-					SIZE_ID[15],
-					SIZE_ID[10],
+					{W: 300, H: 250},
 				},
 			},
 			Video: &openrtb2.Video{
@@ -1165,10 +1026,11 @@ func TestOpenRTBResponseSettingOfNetworkId(t *testing.T) {
 		} else {
 			givenBidExt = nil
 		}
+
 		givenBidResponse := rubiconBidResponse{
 			SeatBid: []rubiconSeatBid{{Buyer: scenario.buyer,
-				SeatBid: openrtb2.SeatBid{
-					Bid: []openrtb2.Bid{{Price: 123.2, ImpID: "test-imp-id", Ext: givenBidExt}}}}},
+				Bid: []rubiconBid{{
+					Bid: openrtb2.Bid{Price: 123.2, ImpID: "test-imp-id", Ext: givenBidExt}}}}},
 		}
 		body, _ := json.Marshal(&givenBidResponse)
 		httpResp := &adapters.ResponseData{
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-banner.json b/adapters/rubicon/rubicontest/exemplary/simple-banner.json
new file mode 100644
index 00000000..a37c0ae9
--- /dev/null
+++ b/adapters/rubicon/rubicontest/exemplary/simple-banner.json
@@ -0,0 +1,380 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    },
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "site": {
+      "pagecat": [
+        "val1",
+        "val2"
+      ],
+      "content": {
+        "data": [
+          {
+            "ext": {
+              "segtax": 1
+            },
+            "segment": [
+              {
+                "id": "segmentId1"
+              },
+              {
+                "id": "segmentId2"
+              }
+            ]
+          },
+          {
+            "ext": {
+              "segtax": "1"
+            },
+            "segment": [
+              {
+                "id": "shouldNotBeCopied"
+              }
+            ]
+          },
+          {
+            "ext": {
+              "segtax": 2
+            },
+            "segment": [
+              {
+                "id": "segmentId3"
+              }
+            ]
+          }
+        ]
+      }
+    },
+    "user": {
+      "yob": 2000,
+      "geo": {
+        "country": "USA",
+        "lat": 47.627500,
+        "lon": -122.346200
+      },
+      "gender": "f",
+      "data": [
+        {
+          "ext": {
+            "segtax": 4
+          },
+          "segment": [
+            {
+              "id": "idToCopy"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": "someValue"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": "4"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": 4
+          },
+          "segment": [
+            {
+              "id": "idToCopy2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": [
+              4
+            ]
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied3"
+            }
+          ]
+        }
+      ]
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "instl": 1,
+        "banner": {
+          "w": 1024,
+          "h": 576
+        },
+        "ext": {
+          "data": {
+            "pbadslot": "pbadslot"
+          },
+          "bidder": {
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "uri?tk_xint=pbs-test-tracker",
+        "body": {
+          "id": "test-request-id",
+          "device": {
+            "ext": {
+              "rp": {
+                "pixelratio": 0
+              }
+            },
+            "ip": "123.123.123.123",
+            "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+          },
+          "site": {
+            "pagecat": [
+              "val1",
+              "val2"
+            ],
+            "content": {
+              "data": [
+                {
+                  "ext": {
+                    "segtax": 1
+                  },
+                  "segment": [
+                    {
+                      "id": "segmentId1"
+                    },
+                    {
+                      "id": "segmentId2"
+                    }
+                  ]
+                },
+                {
+                  "ext": {
+                    "segtax": "1"
+                  },
+                  "segment": [
+                    {
+                      "id": "shouldNotBeCopied"
+                    }
+                  ]
+                },
+                {
+                  "ext": {
+                    "segtax": 2
+                  },
+                  "segment": [
+                    {
+                      "id": "segmentId3"
+                    }
+                  ]
+                }
+              ]
+            },
+            "ext": {
+              "rp": {
+                "site_id": 113932,
+                "target": {
+                  "iab": [
+                    "segmentId1",
+                    "segmentId2",
+                    "segmentId3"
+                  ]
+                }
+              }
+            },
+            "publisher": {
+              "ext": {
+                "rp": {
+                  "account_id": 1001
+                }
+              }
+            }
+          },
+          "user": {
+            "data": [
+              {
+                "ext": {
+                  "segtax": 4
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": "someValue"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": "4"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": 4
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": [
+                    4
+                  ]
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied3"
+                  }
+                ]
+              }
+            ],
+            "ext": {
+              "rp": {
+                "target": {
+                  "iab": [
+                    "idToCopy",
+                    "idToCopy2"
+                  ]
+                }
+              }
+            }
+          },
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "instl": 1,
+              "banner": {
+                "ext": {
+                  "rp": {
+                    "mime": "text/html"
+                  }
+                },
+                "w": 1024,
+                "h": 576
+              },
+              "ext": {
+                "rp": {
+                  "target": {
+                    "pbadslot": "pbadslot",
+                    "pagecat": [
+                      "val1",
+                      "val2"
+                    ]
+                  },
+                  "track": {
+                    "mint": "",
+                    "mint_version": ""
+                  },
+                  "zone_id": 535510
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "buyer": "123",
+              "bid": [
+                {
+                  "id": "test_bid_id",
+                  "impid": "test-imp-id",
+                  "price": 0.27543,
+                  "adm": "some-test-ad",
+                  "cid": "test_cid",
+                  "crid": "test_crid",
+                  "dealid": "test_dealid",
+                  "ext": {
+                    "prebid": {
+                      "meta": {
+                        "networkId": 123
+                      },
+                      "type": "banner"
+                    }
+                  }
+                }
+              ],
+              "seat": "adman"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "some-test-ad",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "ext": {
+              "prebid": {
+                "meta": {
+                  "networkId": 123
+                },
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-native.json b/adapters/rubicon/rubicontest/exemplary/simple-native.json
new file mode 100644
index 00000000..39f6295a
--- /dev/null
+++ b/adapters/rubicon/rubicontest/exemplary/simple-native.json
@@ -0,0 +1,385 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+    },
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "site": {
+      "pagecat": [
+        "val1",
+        "val2"
+      ],
+      "content": {
+        "data": [
+          {
+            "ext": {
+              "segtax": 1
+            },
+            "segment": [
+              {
+                "id": "segmentId1"
+              },
+              {
+                "id": "segmentId2"
+              }
+            ]
+          },
+          {
+            "ext": {
+              "segtax": "1"
+            },
+            "segment": [
+              {
+                "id": "shouldNotBeCopied"
+              }
+            ]
+          },
+          {
+            "ext": {
+              "segtax": 2
+            },
+            "segment": [
+              {
+                "id": "segmentId3"
+              }
+            ]
+          }
+        ]
+      }
+    },
+    "user": {
+      "yob": 2000,
+      "geo": {
+        "country": "USA",
+        "lat": 47.627500,
+        "lon": -122.346200
+      },
+      "gender": "f",
+      "data": [
+        {
+          "ext": {
+            "segtax": 4
+          },
+          "segment": [
+            {
+              "id": "idToCopy"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": "someValue"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": "4"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": 4
+          },
+          "segment": [
+            {
+              "id": "idToCopy2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "segtax": [
+              4
+            ]
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied3"
+            }
+          ]
+        }
+      ]
+    },
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "instl": 1,
+        "native": {
+          "ver": "1.2",
+          "request": "{\"eventtrackers\":[],\"context\":1,\"plcmttype\":2}"
+        },
+        "ext": {
+          "data": {
+            "pbadslot": "pbadslot"
+          },
+          "bidder": {
+            "video": {
+            },
+            "accountId": 1001,
+            "siteId": 113932,
+            "zoneId": 535510
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "uri?tk_xint=pbs-test-tracker",
+        "body": {
+          "id": "test-request-id",
+          "device": {
+            "ext": {
+              "rp": {
+                "pixelratio": 0
+              }
+            },
+            "ip": "123.123.123.123",
+            "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
+          },
+          "site": {
+            "pagecat": [
+              "val1",
+              "val2"
+            ],
+            "content": {
+              "data": [
+                {
+                  "ext": {
+                    "segtax": 1
+                  },
+                  "segment": [
+                    {
+                      "id": "segmentId1"
+                    },
+                    {
+                      "id": "segmentId2"
+                    }
+                  ]
+                },
+                {
+                  "ext": {
+                    "segtax": "1"
+                  },
+                  "segment": [
+                    {
+                      "id": "shouldNotBeCopied"
+                    }
+                  ]
+                },
+                {
+                  "ext": {
+                    "segtax": 2
+                  },
+                  "segment": [
+                    {
+                      "id": "segmentId3"
+                    }
+                  ]
+                }
+              ]
+            },
+            "ext": {
+              "rp": {
+                "site_id": 113932,
+                "target": {
+                  "iab": [
+                    "segmentId1",
+                    "segmentId2",
+                    "segmentId3"
+                  ]
+                }
+              }
+            },
+            "publisher": {
+              "ext": {
+                "rp": {
+                  "account_id": 1001
+                }
+              }
+            }
+          },
+          "user": {
+            "data": [
+              {
+                "ext": {
+                  "segtax": 4
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": "someValue"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": "4"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": 4
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "segtax": [
+                    4
+                  ]
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied3"
+                  }
+                ]
+              }
+            ],
+            "ext": {
+              "rp": {
+                "target": {
+                  "iab": [
+                    "idToCopy",
+                    "idToCopy2"
+                  ]
+                }
+              }
+            }
+          },
+          "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+          },
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "instl": 1,
+              "native": {
+                "ver": "1.2",
+                "request": "{\"eventtrackers\":[],\"context\":1,\"plcmttype\":2}",
+                "request_native": {
+                  "context": 1,
+                  "eventtrackers": [
+                  ],
+                  "plcmttype": 2
+                }
+              },
+              "ext": {
+                "rp": {
+                  "target": {
+                    "pbadslot": "pbadslot",
+                    "pagecat": [
+                      "val1",
+                      "val2"
+                    ]
+                  },
+                  "track": {
+                    "mint": "",
+                    "mint_version": ""
+                  },
+                  "zone_id": 535510
+                }
+              }
+            }
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "buyer": "123",
+              "bid": [
+                {
+                  "id": "test_bid_id",
+                  "impid": "test-imp-id",
+                  "price": 0.27543,
+                  "cid": "test_cid",
+                  "crid": "test_crid",
+                  "dealid": "test_dealid",
+                  "admobject": {
+                    "property1": "value1"
+                  },
+                  "ext": {
+                    "prebid": {
+                      "meta": {
+                        "networkId": 123
+                      },
+                      "type": "native"
+                    }
+                  }
+                }
+              ],
+              "seat": "adman"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test_bid_id",
+            "impid": "test-imp-id",
+            "price": 0.27543,
+            "adm": "{\"property1\":\"value1\"}",
+            "cid": "test_cid",
+            "crid": "test_crid",
+            "dealid": "test_dealid",
+            "ext": {
+              "prebid": {
+                "meta": {
+                  "networkId": 123
+                },
+                "type": "native"
+              }
+            }
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/smaato/native_test.go b/adapters/smaato/native_test.go
new file mode 100644
index 00000000..7ccabe83
--- /dev/null
+++ b/adapters/smaato/native_test.go
@@ -0,0 +1,42 @@
+package smaato
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestExtractAdmNative(t *testing.T) {
+	tests := []struct {
+		testName         string
+		adMarkup         string
+		expectedAdMarkup string
+		expectedError    string
+	}{
+		{
+			testName:         "extract native",
+			adMarkup:         "{\"native\":{\"assets\":[]}}",
+			expectedAdMarkup: `{"assets":[]}`,
+			expectedError:    "",
+		},
+		{
+			testName:         "invalid adMarkup",
+			adMarkup:         "{",
+			expectedAdMarkup: "",
+			expectedError:    "Invalid ad markup {.",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.testName, func(t *testing.T) {
+			adMarkup, err := extractAdmNative(tt.adMarkup)
+
+			if tt.expectedError != "" {
+				assert.EqualError(t, err, tt.expectedError)
+			} else {
+				assert.NoError(t, err)
+			}
+
+			assert.Equal(t, tt.expectedAdMarkup, adMarkup)
+		})
+	}
+}
diff --git a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json
index c30a9a6a..2901bcfc 100644
--- a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json
+++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json
@@ -162,7 +162,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
@@ -268,7 +268,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/exemplary/multiple-media-types.json b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json
index a7d97666..f5f4af33 100644
--- a/adapters/smaato/smaatotest/exemplary/multiple-media-types.json
+++ b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json
@@ -152,7 +152,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
@@ -258,7 +258,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/exemplary/native.json b/adapters/smaato/smaatotest/exemplary/native.json
new file mode 100644
index 00000000..2f3f4823
--- /dev/null
+++ b/adapters/smaato/smaatotest/exemplary/native.json
@@ -0,0 +1,150 @@
+{
+    "mockBidRequest": {
+        "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+        "imp": [
+            {
+                "id": "postbid_iframe",
+                "native": {
+                    "ver": "1.2",
+                    "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":4,\"required\":1,\"img\":{\"type\":3,\"w\":150,\"h\":50}},{\"id\":2,\"required\":1,\"img\":{\"type\":2,\"w\":50,\"h\":50}},{\"id\":0,\"required\":1,\"title\":{\"len\":80}},{\"id\":1,\"required\":1,\"data\":{\"type\":1}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}},{\"id\":5,\"required\":1,\"data\":{\"type\":3}},{\"id\":6,\"required\":1,\"data\":{\"type\":4}},{\"id\":7,\"required\":1,\"data\":{\"type\":5}},{\"id\":8,\"required\":1,\"data\":{\"type\":6}},{\"id\":9,\"required\":1,\"data\":{\"type\":7}},{\"id\":10,\"required\":0,\"data\":{\"type\":8}},{\"id\":11,\"required\":1,\"data\":{\"type\":9}},{\"id\":12,\"require\":0,\"data\":{\"type\":10}},{\"id\":13,\"required\":0,\"data\":{\"type\":11}},{\"id\":14,\"required\":1,\"data\":{\"type\":12}}]}"
+                },
+                "instl": 1,
+                "bidfloor": 0.00123,
+                "ext": {
+                    "bidder": {
+                        "publisherId": "1100042525",
+                        "adspaceId": "130563103"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "publisher": {
+                "id": "1100042525"
+            },
+            "ext": {
+                "data": {},
+                "amp": 0
+            }
+        },
+        "device": {
+            "ua": "test-user-agent"
+        },
+        "user": {
+            "ext": {
+                "data": {}
+            }
+        },
+        "ext": {
+            "prebid": {
+                "auctiontimestamp": 1598262728811,
+                "targeting": {
+                    "includewinners": true,
+                    "includebidderkeys": false
+                }
+            }
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ]
+                },
+                "uri": "https://prebid/bidder",
+                "body": {
+                    "id": "447a0a1d-389d-4730-a418-3777e95de7bd",
+                    "imp": [
+                        {
+                            "id": "postbid_iframe",
+                            "tagid": "130563103",
+                            "bidfloor": 0.00123,
+                            "instl": 1,
+                            "native": {
+                                "ver": "1.2",
+                                "request": "{\"ver\":\"1.2\",\"assets\":[{\"id\":4,\"required\":1,\"img\":{\"type\":3,\"w\":150,\"h\":50}},{\"id\":2,\"required\":1,\"img\":{\"type\":2,\"w\":50,\"h\":50}},{\"id\":0,\"required\":1,\"title\":{\"len\":80}},{\"id\":1,\"required\":1,\"data\":{\"type\":1}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}},{\"id\":5,\"required\":1,\"data\":{\"type\":3}},{\"id\":6,\"required\":1,\"data\":{\"type\":4}},{\"id\":7,\"required\":1,\"data\":{\"type\":5}},{\"id\":8,\"required\":1,\"data\":{\"type\":6}},{\"id\":9,\"required\":1,\"data\":{\"type\":7}},{\"id\":10,\"required\":0,\"data\":{\"type\":8}},{\"id\":11,\"required\":1,\"data\":{\"type\":9}},{\"id\":12,\"require\":0,\"data\":{\"type\":10}},{\"id\":13,\"required\":0,\"data\":{\"type\":11}},{\"id\":14,\"required\":1,\"data\":{\"type\":12}}]}"
+                            }
+                        }
+                    ],
+                    "user": {
+                        "ext": {
+                        }
+                    },
+                    "device": {
+                        "ua": "test-user-agent"
+                    },
+                    "site": {
+                        "publisher": {
+                            "id": "1100042525"
+                        }
+                    },
+                    "ext": {
+                        "client": "prebid_server_0.5"
+                    }
+                }
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "5ebea288-f13a-4754-be6d-4ade66c68877",
+                    "seatbid": [
+                        {
+                            "seat": "CM6523",
+                            "bid": [
+                                {
+                                    "adm": "{\"native\": {\"assets\":[{\"id\": 1, \"img\": {\"type\": 3, \"url\": \"https://smaato.com/image.png\", \"w\": 480, \"h\": 320}}], \"link\": {\"url\": \"https://www.smaato.com\"}}}",
+                                    "adomain": [
+                                        "smaato.com"
+                                    ],
+                                    "bidderName": "smaato",
+                                    "cid": "CM6523",
+                                    "crid": "CR69381",
+                                    "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+                                    "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+                                    "iurl": "https://iurl",
+                                    "nurl": "https://nurl",
+                                    "price": 0.01,
+                                    "w": 480,
+                                    "h": 320
+                                }
+                            ]
+                        }
+                    ],
+                    "bidid": "04db8629-179d-4bcd-acce-e54722969006",
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "adm": "{\"assets\":[{\"id\":1,\"img\":{\"type\":3,\"url\":\"https://smaato.com/image.png\",\"w\":480,\"h\":320}}],\"link\":{\"url\":\"https://www.smaato.com\"}}",
+                        "adomain": [
+                            "smaato.com"
+                        ],
+                        "cid": "CM6523",
+                        "crid": "CR69381",
+                        "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd",
+                        "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C",
+                        "iurl": "https://iurl",
+                        "nurl": "https://nurl",
+                        "price": 0.01,
+                        "w": 480,
+                        "h": 320,
+                        "exp": 300
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json
index cd29d93a..4f38f261 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json
@@ -160,7 +160,7 @@
             "keywords": "keywords"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json
index 8ddc9a72..aac01989 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json
@@ -164,7 +164,7 @@
             "keywords": "keywords"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json
index f0fe35ff..42f516c7 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json
@@ -129,7 +129,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json
index fee49ee9..102af452 100644
--- a/adapters/smaato/smaatotest/exemplary/simple-banner.json
+++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json
@@ -129,7 +129,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json
index fc94c2d4..33eb026c 100644
--- a/adapters/smaato/smaatotest/exemplary/video-app.json
+++ b/adapters/smaato/smaatotest/exemplary/video-app.json
@@ -165,7 +165,7 @@
                         "keywords": "keywords"
                     },
                     "ext": {
-                        "client": "prebid_server_0.4"
+                        "client": "prebid_server_0.5"
                     }
                 }
             },
diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json
index 853a63b2..03654bfb 100644
--- a/adapters/smaato/smaatotest/exemplary/video.json
+++ b/adapters/smaato/smaatotest/exemplary/video.json
@@ -126,7 +126,7 @@
                         }
                     },
                     "ext": {
-                        "client": "prebid_server_0.4"
+                        "client": "prebid_server_0.5"
                     }
                 }
             },
diff --git a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json
index 14b966f9..d5bbd440 100644
--- a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json
+++ b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
index efeba9ed..0e898e8e 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json
index b066dc1b..0b3e6dcd 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json
index 065b6395..980dc356 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json
index 2b59495c..6f5bfc21 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json
@@ -7,7 +7,7 @@
     "imp": [
       {
         "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
-        "native": {
+        "unupported": {
         },
         "ext": {
           "bidder": {
@@ -20,7 +20,7 @@
   },
   "expectedMakeRequestsErrors": [
     {
-      "value": "Invalid MediaType. Smaato only supports Banner and Video.",
+      "value": "Invalid MediaType. Smaato only supports Banner, Video and Native.",
       "comparison": "literal"
     }
   ]
diff --git a/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json
index bfe3dbe2..09c0d97e 100644
--- a/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json
+++ b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json
index 1d59e96d..4f1ddca4 100644
--- a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json
+++ b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json
@@ -107,7 +107,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/expires-header-response.json b/adapters/smaato/smaatotest/supplemental/expires-header-response.json
index be057419..2002f8f3 100644
--- a/adapters/smaato/smaatotest/supplemental/expires-header-response.json
+++ b/adapters/smaato/smaatotest/supplemental/expires-header-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/no-bid-response.json b/adapters/smaato/smaatotest/supplemental/no-bid-response.json
index 4f674f2a..de282d6d 100644
--- a/adapters/smaato/smaatotest/supplemental/no-bid-response.json
+++ b/adapters/smaato/smaatotest/supplemental/no-bid-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json
index b33fea6e..a2883b05 100644
--- a/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json
+++ b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json
@@ -72,7 +72,7 @@
             }
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json
index abab70fa..138e0ac8 100644
--- a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json
+++ b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json
@@ -125,7 +125,7 @@
             "keywords": "power tools"
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/video/multiple-adpods.json b/adapters/smaato/smaatotest/video/multiple-adpods.json
index e5023d87..1f098c92 100644
--- a/adapters/smaato/smaatotest/video/multiple-adpods.json
+++ b/adapters/smaato/smaatotest/video/multiple-adpods.json
@@ -234,7 +234,7 @@
                         }
                     },
                     "ext": {
-                        "client": "prebid_server_0.4"
+                        "client": "prebid_server_0.5"
                     }
                 }
             },
@@ -386,7 +386,7 @@
                         }
                     },
                     "ext": {
-                        "client": "prebid_server_0.4"
+                        "client": "prebid_server_0.5"
                     }
                 }
             },
diff --git a/adapters/smaato/smaatotest/video/single-adpod.json b/adapters/smaato/smaatotest/video/single-adpod.json
index a5f0c059..404e3551 100644
--- a/adapters/smaato/smaatotest/video/single-adpod.json
+++ b/adapters/smaato/smaatotest/video/single-adpod.json
@@ -180,7 +180,7 @@
                         }
                     },
                     "ext": {
-                        "client": "prebid_server_0.4"
+                        "client": "prebid_server_0.5"
                     }
                 }
             },
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json
index 08803d18..22e40ba1 100644
--- a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json
@@ -176,7 +176,7 @@
             }
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json
index 75e28836..ca6cc068 100644
--- a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json
+++ b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json
@@ -176,7 +176,7 @@
             }
           },
           "ext": {
-            "client": "prebid_server_0.4"
+            "client": "prebid_server_0.5"
           }
         }
       },
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index 35cf4f1e..83155316 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -9,6 +9,7 @@ import (
 	"net/http/httptest"
 	"strings"
 	"testing"
+	"testing/iotest"
 	"time"
 
 	"github.com/prebid/prebid-server/analytics"
@@ -684,7 +685,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 		},
 		{
 			description:      "HTTP Read Error",
-			givenBody:        ErrReader(errors.New("anyError")),
+			givenBody:        iotest.ErrReader(errors.New("anyError")),
 			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
 			givenCCPAEnabled: true,
 			expectedError:    "Failed to read request body",
@@ -1826,20 +1827,6 @@ func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, accountID string)
 	return nil, []error{errors.New("Account not found")}
 }
 
-// ErrReader returns an io.Reader that returns 0, err from all Read calls. This is added in
-// Go 1.16. Copied here for now until we switch over.
-func ErrReader(err error) io.Reader {
-	return &errReader{err: err}
-}
-
-type errReader struct {
-	err error
-}
-
-func (r *errReader) Read(p []byte) (int, error) {
-	return 0, r.err
-}
-
 type fakePermissions struct {
 }
 
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index e0332b30..96252c8b 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/julienschmidt/httprouter"
 	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/amp"
 	"github.com/prebid/prebid-server/analytics"
 	analyticsConf "github.com/prebid/prebid-server/analytics/config"
 	"github.com/prebid/prebid-server/config"
@@ -47,6 +48,7 @@ func TestGoodAmpRequests(t *testing.T) {
 			desc: "Valid, consent handling in query",
 			dir:  "sample-requests/amp/consent-through-query/",
 			testFiles: []string{
+				"addtl-consent-through-query.json",
 				"gdpr-tcf1-consent-through-query.json",
 				"gdpr-tcf2-consent-through-query.json",
 				"gdpr-legacy-tcf2-consent-through-query.json",
@@ -376,6 +378,237 @@ func TestGDPRConsent(t *testing.T) {
 	}
 }
 
+func TestOverrideWithParams(t *testing.T) {
+	e := &endpointDeps{
+		cfg: &config.Configuration{
+			GDPR: config.GDPR{
+				Enabled: true,
+			},
+		},
+	}
+
+	type testInput struct {
+		ampParams  amp.Params
+		bidRequest *openrtb2.BidRequest
+	}
+	type testOutput struct {
+		bidRequest *openrtb2.BidRequest
+		errorMsgs  []string
+	}
+	testCases := []struct {
+		desc     string
+		given    testInput
+		expected testOutput
+	}{
+		{
+			desc: "bid request with no Site field - amp.Params empty - expect Site to be added",
+			given: testInput{
+				ampParams: amp.Params{},
+				bidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+				},
+			},
+			expected: testOutput{
+				bidRequest: &openrtb2.BidRequest{
+					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
+				},
+				errorMsgs: nil,
+			},
+		},
+		{
+			desc: "amp.Params with Size field - expect Site and Banner format fields to be added",
+			given: testInput{
+				ampParams: amp.Params{
+					Size: amp.Size{
+						Width:  480,
+						Height: 320,
+					},
+				},
+				bidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+				},
+			},
+			expected: testOutput{
+				bidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{
+							Banner: &openrtb2.Banner{
+								Format: []openrtb2.Format{
+									{
+										W: 480,
+										H: 320,
+									},
+								},
+							},
+						},
+					},
+					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
+				},
+				errorMsgs: nil,
+			},
+		},
+		{
+			desc: "amp.Params with CanonicalURL field - expect Site to be aded with Page and Domain fields",
+			given: testInput{
+				ampParams:  amp.Params{CanonicalURL: "http://www.foobar.com"},
+				bidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}},
+			},
+			expected: testOutput{
+				bidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+					Site: &openrtb2.Site{
+						Page:   "http://www.foobar.com",
+						Domain: "www.foobar.com",
+						Ext:    json.RawMessage(`{"amp":1}`),
+					},
+				},
+				errorMsgs: nil,
+			},
+		},
+		{
+			desc: "bid request with malformed User.Ext - amp.Params with AdditionalConsent - expect error",
+			given: testInput{
+				ampParams: amp.Params{AdditionalConsent: "1~X.X.X.X"},
+				bidRequest: &openrtb2.BidRequest{
+					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+					User: &openrtb2.User{Ext: json.RawMessage(`malformed`)},
+				},
+			},
+			expected: testOutput{
+				bidRequest: &openrtb2.BidRequest{
+					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
+					User: &openrtb2.User{Ext: json.RawMessage(`malformed`)},
+				},
+				errorMsgs: []string{"invalid character 'm' looking for beginning of value"},
+			},
+		},
+		{
+			desc: "bid request with valid imp[0].ext - amp.Params with malformed targeting value - expect error because imp[0].ext won't be unable to get merged with targeting values",
+			given: testInput{
+				ampParams: amp.Params{Targeting: "{123,}"},
+				bidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{
+							Banner: &openrtb2.Banner{Format: []openrtb2.Format{}},
+							Ext:    []byte(`{"appnexus":{"placementId":123}}`),
+						},
+					},
+				},
+			},
+			expected: testOutput{
+				bidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{
+							Banner: &openrtb2.Banner{Format: []openrtb2.Format{}},
+							Ext:    json.RawMessage(`{"appnexus":{"placementId":123}}`),
+						},
+					},
+					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
+				},
+				errorMsgs: []string{"unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch"},
+			},
+		},
+		{
+			desc: "bid request with malformed user.ext.prebid - amp.Params with GDPR consent values - expect policy writer to return error",
+			given: testInput{
+				ampParams: amp.Params{
+					ConsentType: amp.ConsentTCF2,
+					Consent:     "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
+				},
+				bidRequest: &openrtb2.BidRequest{
+					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
+				},
+			},
+			expected: testOutput{
+				bidRequest: &openrtb2.BidRequest{
+					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
+					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
+					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
+				},
+				errorMsgs: []string{"invalid character 'm' looking for beginning of object key string"},
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		errs := e.overrideWithParams(test.given.ampParams, test.given.bidRequest)
+
+		assert.Equal(t, test.expected.bidRequest, test.given.bidRequest, test.desc)
+		assert.Len(t, errs, len(test.expected.errorMsgs), test.desc)
+		if len(test.expected.errorMsgs) > 0 {
+			assert.Equal(t, test.expected.errorMsgs[0], errs[0].Error(), test.desc)
+		}
+	}
+}
+
+func TestSetConsentedProviders(t *testing.T) {
+
+	sampleBidRequest := &openrtb2.BidRequest{}
+
+	testCases := []struct {
+		description            string
+		givenAdditionalConsent string
+		givenBidRequest        *openrtb2.BidRequest
+		expectedBidRequest     *openrtb2.BidRequest
+		expectedError          bool
+	}{
+		{
+			description:            "empty additional consent bid request unmodified",
+			givenAdditionalConsent: "",
+			givenBidRequest:        sampleBidRequest,
+			expectedBidRequest:     sampleBidRequest,
+			expectedError:          false,
+		},
+		{
+			description:            "nil bid request, expect error",
+			givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING",
+			givenBidRequest:        nil,
+			expectedBidRequest:     nil,
+			expectedError:          true,
+		},
+		{
+			description:            "malformed user.ext, expect error",
+			givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING",
+			givenBidRequest: &openrtb2.BidRequest{
+				User: &openrtb2.User{
+					Ext: json.RawMessage(`malformed`),
+				},
+			},
+			expectedBidRequest: &openrtb2.BidRequest{
+				User: &openrtb2.User{
+					Ext: json.RawMessage(`malformed`),
+				},
+			},
+			expectedError: true,
+		},
+		{
+			description:            "non-empty additional consent bid request will carry this value in user.ext.ConsentedProvidersSettings.consented_providers",
+			givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING",
+			givenBidRequest:        sampleBidRequest,
+			expectedBidRequest: &openrtb2.BidRequest{
+				User: &openrtb2.User{
+					Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ADDITIONAL_CONSENT_STRING"}}`),
+				},
+			},
+			expectedError: false,
+		},
+	}
+
+	for _, test := range testCases {
+		err := setConsentedProviders(test.givenBidRequest, amp.Params{AdditionalConsent: test.givenAdditionalConsent})
+
+		if test.expectedError {
+			assert.Error(t, err, test.description)
+		} else {
+			assert.NoError(t, err, test.description)
+		}
+		assert.Equal(t, test.expectedBidRequest, test.givenBidRequest, test.description)
+	}
+}
+
 func TestCCPAConsent(t *testing.T) {
 	consent := "1NYN"
 	existingConsent := "1NNN"
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index 01f11044..d98b1e11 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -207,42 +207,46 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 			description: "None",
 			bidderInfos: map[string]config.BidderInfo{},
 			expected: map[string]string{
-				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
-				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
-				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
-				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+				"lifestreet":     `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":         `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
+				"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 			},
 		},
 		{
 			description: "Enabled",
 			bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled},
 			expected: map[string]string{
-				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
-				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
-				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
-				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+				"lifestreet":     `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":         `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
+				"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 			},
 		},
 		{
 			description: "Disabled",
 			bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled},
 			expected: map[string]string{
-				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
-				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
-				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
-				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
-				"appnexus":     `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
+				"lifestreet":     `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":         `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
+				"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+				"appnexus":       `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
+				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 			},
 		},
 		{
 			description: "Mixed",
 			bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled},
 			expected: map[string]string{
-				"lifestreet":   `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
-				"adagio":       `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
-				"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
-				"yssp":         `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
-				"appnexus":     `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
+				"lifestreet":     `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
+				"adagio":         `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
+				"somoaudience":   `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
+				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
+				"appnexus":       `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
+				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 			},
 		},
 	}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 42044a11..a77f2f85 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -4196,13 +4196,13 @@ func TestCallSignHeader(t *testing.T) {
 }
 
 /*
-	TestOverrideConfigAlternateBidderCodesWithRequestValues makes sure that the correct alternabiddercodes list is forwarded to the adapters and only the approved bids are returned in auction response.
+TestOverrideConfigAlternateBidderCodesWithRequestValues makes sure that the correct alternabiddercodes list is forwarded to the adapters and only the approved bids are returned in auction response.
 
-	1. request.ext.prebid.alternatebiddercodes has priority over the content of config.Account.Alternatebiddercodes.
+1. request.ext.prebid.alternatebiddercodes has priority over the content of config.Account.Alternatebiddercodes.
 
-	2. request is updated with config.Account.Alternatebiddercodes values if request.ext.prebid.alternatebiddercodes is empty or not specified.
+2. request is updated with config.Account.Alternatebiddercodes values if request.ext.prebid.alternatebiddercodes is empty or not specified.
 
-	3. request.ext.prebid.alternatebiddercodes is given priority over config.Account.Alternatebiddercodes if both are specified.
+3. request.ext.prebid.alternatebiddercodes is given priority over config.Account.Alternatebiddercodes if both are specified.
 */
 func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) {
 	type testIn struct {
diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go
index 3d21bad2..a2d8cd51 100644
--- a/openrtb_ext/request_wrapper_test.go
+++ b/openrtb_ext/request_wrapper_test.go
@@ -16,6 +16,7 @@ func TestUserExt(t *testing.T) {
 	assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent")
 	assert.Nil(t, userExt.GetEid(), "Empty UserExt should have nil eid")
 	assert.Nil(t, userExt.GetPrebid(), "Empty UserExt should have nil prebid")
+	assert.Nil(t, userExt.GetConsentedProvidersSettingsIn(), "Empty UserExt should have nil consentedProvidersSettings")
 
 	newConsent := "NewConsent"
 	userExt.SetConsent(&newConsent)
@@ -31,12 +32,29 @@ func TestUserExt(t *testing.T) {
 	userExt.SetPrebid(&newPrebid)
 	assert.Equal(t, ExtUserPrebid{BuyerUIDs: buyerIDs}, *userExt.GetPrebid(), "UserExt prebid is incorrect")
 
+	consentedProvidersSettings := &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~X.X.X"}
+	userExt.SetConsentedProvidersSettingsIn(consentedProvidersSettings)
+	assert.Equal(t, &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~X.X.X"}, userExt.GetConsentedProvidersSettingsIn(), "UserExt consentedProvidersSettings is incorrect")
 	assert.Equal(t, true, userExt.Dirty(), "UserExt should be dirty after field updates")
+	cpsIn := userExt.GetConsentedProvidersSettingsIn()
+	assert.Equal(t, "1~X.X.X", cpsIn.ConsentedProvidersString, "UserExt consentedProviders is incorrect")
+
+	consentedProvidersString := "1~1.35.41.101"
+	cpsIn.ConsentedProvidersString = consentedProvidersString
+	userExt.SetConsentedProvidersSettingsIn(cpsIn)
+	cpsIn = userExt.GetConsentedProvidersSettingsIn()
+	assert.Equal(t, "1~1.35.41.101", cpsIn.ConsentedProvidersString, "UserExt consentedProviders is incorrect")
+
+	cpsOut := &ConsentedProvidersSettingsOut{}
+	//cpsOut.ConsentedProvidersList = make([]int, 0, 1)
+	cpsOut.ConsentedProvidersList = append(cpsOut.ConsentedProvidersList, ParseConsentedProvidersString(consentedProvidersString)...)
+	assert.Len(t, cpsOut.ConsentedProvidersList, 4, "UserExt consentedProvidersList is incorrect")
+	userExt.SetConsentedProvidersSettingsOut(cpsOut)
 
 	updatedUserExt, err := userExt.marshal()
 	assert.Nil(t, err, "Marshalling UserExt after updating should not cause an error")
 
-	expectedUserExt := json.RawMessage(`{"consent":"NewConsent","prebid":{"buyeruids":{"buyer":"id"}},"eids":[{"source":"source","uids":[{"id":"id"}]}]}`)
+	expectedUserExt := json.RawMessage(`{"consent":"NewConsent","prebid":{"buyeruids":{"buyer":"id"}},"consented_providers_settings":{"consented_providers":[1,35,41,101]},"ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"},"eids":[{"source":"source","uids":[{"id":"id"}]}]}`)
 	assert.JSONEq(t, string(expectedUserExt), string(updatedUserExt), "Marshalled UserExt is incorrect")
 
 	assert.Equal(t, false, userExt.Dirty(), "UserExt should not be dirty after marshalling")
@@ -109,81 +127,341 @@ func TestRebuildUserExt(t *testing.T) {
 	strA := "a"
 	strB := "b"
 
-	testCases := []struct {
+	type testCase struct {
 		description           string
 		request               openrtb2.BidRequest
 		requestUserExtWrapper UserExt
 		expectedRequest       openrtb2.BidRequest
+	}
+	testGroups := []struct {
+		groupDesc string
+		tests     []testCase
 	}{
 		{
-			description:           "Nil - Not Dirty",
-			request:               openrtb2.BidRequest{},
-			requestUserExtWrapper: UserExt{},
-			expectedRequest:       openrtb2.BidRequest{},
+			groupDesc: "Consent string tests",
+			tests: []testCase{
+				// Nil req.User
+				{
+					description:           "Nil req.User - UserExt Not Dirty",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{},
+				},
+				{
+					description:           "Nil req.User - Dirty UserExt",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+				},
+				{
+					description:           "Nil req.User - Dirty UserExt but consent is nil - No Change",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{},
+				},
+				// Nil req.User.Ext
+				{
+					description:           "Nil req.User.Ext - Not Dirty - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Nil req.User.Ext - Dirty with valid consent string - Expect consent string to be added",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}},
+				},
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with nil consent string- No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				// Not-nil req.User.Ext
+				{
+					description:           "Populated - Not Dirty - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+				},
+				{
+					description:           "Populated - Dirty - Consent string overriden",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+					requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}},
+				},
+				{
+					description:           "Populated - Dirty but consent string is equal to the one already set - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+					requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+				},
+				{
+					description:           "Populated - Dirty UserExt with nil consent string - Cleared",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+					requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+			},
 		},
 		{
-			description:           "Nil - Dirty",
-			request:               openrtb2.BidRequest{},
-			requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+			groupDesc: "Consented provider settings string form",
+			tests: []testCase{
+				// Nil req.User
+				{
+					description:           "Nil req.User - Dirty UserExt with nil consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{},
+				},
+				{
+					description:           "Nil req.User - Dirty UserExt with empty consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{},
+				},
+				{
+					description:           "Nil req.User - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "ConsentedProvidersString"}, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`)}},
+				},
+				// Nil req.User.Ext
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with nil consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: nil, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with empty consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "ConsentedProvidersString"}, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`)}},
+				},
+				// Not-nil req.User.Ext
+				{
+					description:           "Populated req.User.Ext - Not Dirty UserExt - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
+				},
+				{
+					description:           "Populated req.User.Ext - Dirty UserExt with nil consentedProviderSettings - Populated req.User.Ext gets overriden with nil User.Ext",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: nil, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Populated req.User.Ext - Dirty UserExt with empty consentedProviderSettings - Populated req.User.Ext gets overriden with nil User.Ext",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"1~X.X.X.X":{"consented_providers":"1~X.X.X.X"}}`)}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{}, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Populated req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are overriden",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~X.X.X.X"}}`)}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsIn: &ConsentedProvidersSettingsIn{ConsentedProvidersString: "1~1.35.41.101"}, consentedProvidersSettingsInDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"1~1.35.41.101"}}`)}},
+				},
+			},
 		},
 		{
-			description:           "Nil - Dirty - No Change",
-			request:               openrtb2.BidRequest{},
-			requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{},
+			groupDesc: "Consented provider settings array",
+			tests: []testCase{
+				// Nil req.User
+				{
+					description:           "Nil req.User - Dirty UserExt with nil consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{},
+				},
+				{
+					description:           "Nil req.User - Dirty UserExt with empty consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{},
+				},
+				{
+					description:           "Nil req.User - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
+					request:               openrtb2.BidRequest{},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{1, 35, 41, 101}}, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+				},
+				// Nil req.User.Ext
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with nil consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: nil, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with empty consentedProviderSettings - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Nil req.User.Ext - Dirty UserExt with populated consentedProviderSettings - ConsentedProvidersSettings are added",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{1, 35, 41, 101}}, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+				},
+				// Not-nil req.User.Ext
+				{
+					description:           "Populated req.User.Ext - Not Dirty UserExt - No Change",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+					requestUserExtWrapper: UserExt{},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+				},
+				{
+					description:           "Populated req.User.Ext - Dirty UserExt with nil consentedProviderSettingsOut - Populated req.User.Ext gets overriden with nil User.Ext",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: nil, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Populated req.User.Ext - Dirty UserExt with empty consentedProviderSettingsOut - Populated req.User.Ext gets overriden with nil User.Ext",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{}, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+				},
+				{
+					description:           "Populated req.User.Ext - Dirty UserExt with populated consentedProviderSettingsOut - consented_providers list elements are overriden",
+					request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[1,35,41,101]}}`)}},
+					requestUserExtWrapper: UserExt{consentedProvidersSettingsOut: &ConsentedProvidersSettingsOut{ConsentedProvidersList: []int{35, 36, 240}}, consentedProvidersSettingsOutDirty: true},
+					expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[35,36,240]}}`)}},
+				},
+			},
 		},
+	}
+
+	for _, group := range testGroups {
+		for _, test := range group.tests {
+			// create required filed in the test loop to keep test declaration easier to read
+			test.requestUserExtWrapper.ext = make(map[string]json.RawMessage)
+
+			w := RequestWrapper{BidRequest: &test.request, userExt: &test.requestUserExtWrapper}
+			w.RebuildRequest()
+			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		}
+	}
+}
+
+func TestUserExtUnmarshal(t *testing.T) {
+	type testInput struct {
+		userExt *UserExt
+		extJson json.RawMessage
+	}
+	testCases := []struct {
+		desc        string
+		in          testInput
+		expectError bool
+	}{
 		{
-			description:           "Empty - Not Dirty",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{}},
-			requestUserExtWrapper: UserExt{},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+			desc: "UserExt.ext is not nil, don't expect error",
+			in: testInput{
+				userExt: &UserExt{
+					ext: map[string]json.RawMessage{
+						"eids": json.RawMessage(`[{"source":"value"}]`),
+					},
+				},
+				extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`),
+			},
+			expectError: false,
 		},
 		{
-			description:           "Empty - Dirty",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{}},
-			requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}},
+			desc: "UserExt.ext is dirty, don't expect error",
+			in: testInput{
+				userExt: &UserExt{extDirty: true},
+				extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`),
+			},
+			expectError: false,
 		},
+		// Eids
 		{
-			description:           "Empty - Dirty - No Change",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{}},
-			requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+			desc: "Has eids and it is valid JSON",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"eids":[{"source":"value"}]}`),
+			},
+			expectError: false,
 		},
 		{
-			description:           "Populated - Not Dirty",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
-			requestUserExtWrapper: UserExt{},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+			desc: "Has malformed eids expect error",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"eids":123}`),
+			},
+			expectError: true,
 		},
+		// prebid
 		{
-			description:           "Populated - Dirty",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
-			requestUserExtWrapper: UserExt{consent: &strB, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"b"}`)}},
+			desc: "Has prebid and it is valid JSON",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"prebid":{"buyeruids":{"elem1":"value1"}}}`),
+			},
+			expectError: false,
 		},
 		{
-			description:           "Populated - Dirty - No Change",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
-			requestUserExtWrapper: UserExt{consent: &strA, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
+			desc: "Has malformed prebid expect error",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"prebid":{"buyeruids":123}}`),
+			},
+			expectError: true,
 		},
+		// ConsentedProvidersSettings
 		{
-			description:           "Populated - Dirty - Cleared",
-			request:               openrtb2.BidRequest{User: &openrtb2.User{Ext: json.RawMessage(`{"consent":"a"}`)}},
-			requestUserExtWrapper: UserExt{consent: nil, consentDirty: true},
-			expectedRequest:       openrtb2.BidRequest{User: &openrtb2.User{}},
+			desc: "Has ConsentedProvidersSettings and it is valid JSON",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ConsentedProvidersString"}}`),
+			},
+			expectError: false,
+		},
+		{
+			desc: "Has malformed ConsentedProvidersSettings expect error",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":123}}`),
+			},
+			expectError: true,
+		},
+		// consented_providers_settings
+		{
+			desc: "Has consented_providers_settings and it is valid JSON",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"consented_providers_settings":{"consented_providers":[2,25]}}`),
+			},
+			expectError: false,
+		},
+		{
+			desc: "Has malformed consented_providers_settings expect error",
+			in: testInput{
+				userExt: &UserExt{},
+				extJson: json.RawMessage(`{"consented_providers_settings":{"consented_providers":123}}`),
+			},
+			expectError: true,
 		},
 	}
+	for _, tc := range testCases {
+		err := tc.in.userExt.unmarshal(tc.in.extJson)
 
-	for _, test := range testCases {
-		// create required filed in the test loop to keep test declaration easier to read
-		test.requestUserExtWrapper.ext = make(map[string]json.RawMessage)
-
-		w := RequestWrapper{BidRequest: &test.request, userExt: &test.requestUserExtWrapper}
-		w.RebuildRequest()
-		assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
+		if tc.expectError {
+			assert.Error(t, err, tc.desc)
+		} else {
+			assert.NoError(t, err, tc.desc)
+		}
 	}
 }