diff --git a/adapters/rtbstack/params_test.go b/adapters/rtbstack/params_test.go
new file mode 100644
index 00000000000..087302decb5
--- /dev/null
+++ b/adapters/rtbstack/params_test.go
@@ -0,0 +1,57 @@
+package rtbstack
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderRTBStack, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected rtbstack params: %s", validParam)
+ }
+ }
+}
+
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, invalidParam := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderRTBStack, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"route":"https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145","tagId":"12345"}`,
+ `{"route":"https://site.eu-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2","tagId":"tag1"}`,
+ `{"route":"https://site.asia-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2","tagId":"tag1","customParams":{"foo":"bar"}}`,
+ `{"route":"https://example.us-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2","tagId":"tag1","customParams":{}}`,
+ `{"route":"https://example.us-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2","tagId":"tag1","customParams":{"nested":{"key":"value"}}}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"route":"https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2"}`,
+ `{"tagId":"12345"}`,
+ `{"route":"","tagId":"12345"}`,
+ `{"route":123,"tagId":"12345"}`,
+ `{"route":"https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2","tagId":123}`,
+}
diff --git a/adapters/rtbstack/rtbstack.go b/adapters/rtbstack/rtbstack.go
new file mode 100644
index 00000000000..5b5b73f0a39
--- /dev/null
+++ b/adapters/rtbstack/rtbstack.go
@@ -0,0 +1,269 @@
+package rtbstack
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "text/template"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v4/adapters"
+ "github.com/prebid/prebid-server/v4/config"
+ "github.com/prebid/prebid-server/v4/errortypes"
+ "github.com/prebid/prebid-server/v4/macros"
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+ "github.com/prebid/prebid-server/v4/util/jsonutil"
+)
+
+type adapter struct {
+ endpoint *template.Template
+}
+
+// impCtx represents the context containing an OpenRTB impression and its corresponding RTBStack extension configuration.
+type impCtx struct {
+ imp openrtb2.Imp
+ rtbStackExt *openrtb_ext.ExtImpRTBStack
+}
+
+// extImpRTBStack is used for imp->ext when sending to rtb-stack backend.
+type extImpRTBStack struct {
+ CustomParams map[string]interface{} `json:"customParams,omitempty"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ tpl, err := template.New("endpointTemplate").Parse(config.Endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+ }
+
+ bidder := &adapter{
+ endpoint: tpl,
+ }
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ if len(request.Imp) == 0 {
+ return nil, []error{&errortypes.BadInput{
+ Message: "No impressions in request",
+ }}
+ }
+
+ var errs []error
+ impsByRoute := make(map[string][]*impCtx)
+ var routeOrder []string
+
+ for i := range request.Imp {
+ imp, ext, err := preprocessImp(request.Imp[i])
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ ctx := &impCtx{
+ imp: imp,
+ rtbStackExt: ext,
+ }
+ if _, ok := impsByRoute[ext.Route]; !ok {
+ routeOrder = append(routeOrder, ext.Route)
+ }
+ impsByRoute[ext.Route] = append(impsByRoute[ext.Route], ctx)
+ }
+
+ if len(routeOrder) == 0 {
+ return nil, errs
+ }
+
+ var newSite *openrtb2.Site
+ if request.Site != nil && request.Site.Domain == "" {
+ siteCopy := *request.Site
+ pageURL, parseErr := url.Parse(request.Site.Page)
+ if parseErr == nil && pageURL.Hostname() != "" {
+ siteCopy.Domain = pageURL.Hostname()
+ } else {
+ siteCopy.Domain = request.Site.Page
+ }
+ newSite = &siteCopy
+ }
+
+ requests := make([]*adapters.RequestData, 0, len(routeOrder))
+ for _, route := range routeOrder {
+ group := impsByRoute[route]
+
+ endpoint, err := a.buildEndpointURL(group[0].rtbStackExt)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ processedImps := make([]openrtb2.Imp, 0, len(group))
+ for _, v := range group {
+ processedImps = append(processedImps, v.imp)
+ }
+
+ newRequest := *request
+ newRequest.Imp = processedImps
+ if newSite != nil {
+ newRequest.Site = newSite
+ }
+
+ reqJSON, err := jsonutil.Marshal(newRequest)
+ if err != nil {
+ errs = append(errs, &errortypes.FailedToRequestBids{
+ Message: "Error parsing reqJSON object",
+ })
+ continue
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+ requests = append(requests, &adapters.RequestData{
+ Method: http.MethodPost,
+ Uri: endpoint,
+ Body: reqJSON,
+ Headers: headers,
+ ImpIDs: openrtb_ext.GetImpIDs(newRequest.Imp),
+ })
+ }
+
+ if len(requests) == 0 {
+ return nil, errs
+ }
+
+ return requests, errs
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if adapters.IsResponseStatusCodeNoContent(response) {
+ return nil, nil
+ }
+
+ if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
+ return nil, []error{err}
+ }
+
+ var bidResp openrtb2.BidResponse
+ if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ if len(bidResp.SeatBid) == 0 {
+ return nil, nil
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp))
+
+ if bidResp.Cur != "" {
+ bidResponse.Currency = bidResp.Cur
+ }
+
+ var bidErrs []error
+ for _, sb := range bidResp.SeatBid {
+ for i := range sb.Bid {
+ bidType, err := getMediaTypeForBid(sb.Bid[i])
+ if err != nil {
+ bidErrs = append(bidErrs, err)
+ continue
+ }
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &sb.Bid[i],
+ BidType: bidType,
+ })
+ }
+ }
+ return bidResponse, bidErrs
+}
+
+var validRegions = map[string]bool{"us": true, "eu": true, "sg": true}
+
+func (a *adapter) buildEndpointURL(ext *openrtb_ext.ExtImpRTBStack) (string, error) {
+ routeURL, err := url.Parse(ext.Route)
+ if err != nil {
+ return "", &errortypes.BadInput{Message: fmt.Sprintf("invalid route URL: %v", err)}
+ }
+
+ region, err := extractRegion(routeURL.Hostname())
+ if err != nil {
+ return "", err
+ }
+
+ queryParams := routeURL.Query()
+ client := queryParams.Get("client")
+ endpoint := queryParams.Get("endpoint")
+ ssp := queryParams.Get("ssp")
+
+ if client == "" || endpoint == "" || ssp == "" {
+ return "", &errortypes.BadInput{Message: "route URL must contain client, endpoint, and ssp query parameters"}
+ }
+
+ params := macros.EndpointTemplateParams{
+ Region: region,
+ SspID: ssp,
+ ZoneID: endpoint,
+ PartnerId: client,
+ }
+
+ return macros.ResolveMacros(a.endpoint, params)
+}
+
+func extractRegion(hostname string) (string, error) {
+ parts := strings.Split(hostname, ".")
+ for _, part := range parts {
+ if strings.HasSuffix(part, "-adx-admixer") {
+ region := strings.ToLower(strings.TrimSuffix(part, "-adx-admixer"))
+ if validRegions[region] {
+ return region, nil
+ }
+ }
+ }
+ return "", &errortypes.BadInput{Message: "unable to extract valid region from route URL hostname"}
+}
+
+func preprocessImp(
+ imp openrtb2.Imp,
+) (openrtb2.Imp, *openrtb_ext.ExtImpRTBStack, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return imp, nil, &errortypes.BadInput{Message: err.Error()}
+ }
+
+ var impExt openrtb_ext.ExtImpRTBStack
+ if err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt); err != nil {
+ return imp, nil, &errortypes.BadInput{
+ Message: "Wrong RTBStack bidder ext",
+ }
+ }
+
+ imp.TagID = impExt.TagId
+
+ newExt := extImpRTBStack{
+ CustomParams: impExt.CustomParams,
+ }
+
+ newImpExtForRTBStack, err := jsonutil.Marshal(newExt)
+ if err != nil {
+ return imp, nil, &errortypes.BadInput{Message: err.Error()}
+ }
+ imp.Ext = newImpExtForRTBStack
+
+ return imp, &impExt, nil
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+ switch bid.MType {
+ case openrtb2.MarkupBanner:
+ return openrtb_ext.BidTypeBanner, nil
+ case openrtb2.MarkupVideo:
+ return openrtb_ext.BidTypeVideo, nil
+ case openrtb2.MarkupNative:
+ return openrtb_ext.BidTypeNative, nil
+ case openrtb2.MarkupAudio:
+ return openrtb_ext.BidTypeAudio, nil
+ default:
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("unsupported MType %d for bid %s", bid.MType, bid.ImpID),
+ }
+ }
+}
diff --git a/adapters/rtbstack/rtbstack_test.go b/adapters/rtbstack/rtbstack_test.go
new file mode 100644
index 00000000000..a28237d4caf
--- /dev/null
+++ b/adapters/rtbstack/rtbstack_test.go
@@ -0,0 +1,29 @@
+package rtbstack
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/v4/adapters/adapterstest"
+ "github.com/prebid/prebid-server/v4/config"
+ "github.com/prebid/prebid-server/v4/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderRTBStack, config.Adapter{
+ Endpoint: "https://{{.Region}}-adx-admixer.rtb-stack.com/pbs?ssp={{.SspID}}&endpoint={{.ZoneID}}&client={{.PartnerId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "rtbstacktest", bidder)
+}
+
+func TestBuilderInvalidTemplate(t *testing.T) {
+ _, err := Builder(openrtb_ext.BidderRTBStack, config.Adapter{
+ Endpoint: "https://{{.Region}-bad-template"}, config.Server{})
+
+ if err == nil {
+ t.Fatal("Builder should return an error for an invalid endpoint template")
+ }
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/app-audio.json b/adapters/rtbstack/rtbstacktest/exemplary/app-audio.json
new file mode 100644
index 00000000000..437343b0563
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-audio.json
@@ -0,0 +1,162 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-audio-short",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 5,
+ "maxduration": 30
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-audio-short",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-app-audio-long",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 60
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-audio-long",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-audio-short",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 5,
+ "maxduration": 30
+ },
+ "tagid": "app-audio-short",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-app-audio-long",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 60
+ },
+ "tagid": "app-audio-long",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-app-audio-short",
+ "imp-app-audio-long"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-app-audio-short-1",
+ "impid": "imp-app-audio-short",
+ "price": 0.75,
+ "adm": "",
+ "crid": "crid-app-audio-short-1",
+ "mtype": 3
+ },
+ {
+ "id": "bid-app-audio-long-1",
+ "impid": "imp-app-audio-long",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-app-audio-long-1",
+ "mtype": 3
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-app-audio-short-1",
+ "impid": "imp-app-audio-short",
+ "price": 0.75,
+ "adm": "",
+ "crid": "crid-app-audio-short-1",
+ "mtype": 3
+ },
+ "type": "audio"
+ },
+ {
+ "bid": {
+ "id": "bid-app-audio-long-1",
+ "impid": "imp-app-audio-long",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-app-audio-long-1",
+ "mtype": 3
+ },
+ "type": "audio"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/app-banner.json b/adapters/rtbstack/rtbstacktest/exemplary/app-banner.json
new file mode 100644
index 00000000000..1072a3946bd
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-banner.json
@@ -0,0 +1,162 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-app-large",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "tagid": "app-basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-app-large",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "app-large",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-app-basic",
+ "imp-app-large"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-app-basic-1",
+ "impid": "imp-app-basic",
+ "price": 0.95,
+ "adm": "
App Banner 320x50
",
+ "crid": "crid-app-basic-1",
+ "mtype": 1
+ },
+ {
+ "id": "bid-app-large-1",
+ "impid": "imp-app-large",
+ "price": 1.5,
+ "adm": "App Banner 728x90
",
+ "crid": "crid-app-large-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-app-basic-1",
+ "impid": "imp-app-basic",
+ "price": 0.95,
+ "adm": "App Banner 320x50
",
+ "crid": "crid-app-basic-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-app-large-1",
+ "impid": "imp-app-large",
+ "price": 1.5,
+ "adm": "App Banner 728x90
",
+ "crid": "crid-app-large-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/app-native.json b/adapters/rtbstack/rtbstacktest/exemplary/app-native.json
new file mode 100644
index 00000000000..af5549ff6c4
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-native.json
@@ -0,0 +1,146 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-native-small",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":50}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":120,\"hmin\":120,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":100}}]}"
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-native-small",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-app-native-large",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":2,\"adunit\":2,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":300,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":150}}]}"
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-native-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-native-small",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":50}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":120,\"hmin\":120,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":100}}]}"
+ },
+ "tagid": "app-native-small",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-app-native-large",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":2,\"adunit\":2,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":300,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":150}}]}"
+ },
+ "tagid": "app-native-large",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-app-native-small",
+ "imp-app-native-large"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-app-native-small-1",
+ "impid": "imp-app-native-small",
+ "price": 1.5,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Small App Native Ad\"}}]}}",
+ "crid": "crid-app-native-small-1",
+ "mtype": 4
+ },
+ {
+ "id": "bid-app-native-large-1",
+ "impid": "imp-app-native-large",
+ "price": 2.75,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Large App Native Ad\"}}]}}",
+ "crid": "crid-app-native-large-1",
+ "mtype": 4
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-app-native-small-1",
+ "impid": "imp-app-native-small",
+ "price": 1.5,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Small App Native Ad\"}}]}}",
+ "crid": "crid-app-native-small-1",
+ "mtype": 4
+ },
+ "type": "native"
+ },
+ {
+ "bid": {
+ "id": "bid-app-native-large-1",
+ "impid": "imp-app-native-large",
+ "price": 2.75,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Large App Native Ad\"}}]}}",
+ "crid": "crid-app-native-large-1",
+ "mtype": 4
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/app-video.json b/adapters/rtbstack/rtbstacktest/exemplary/app-video.json
new file mode 100644
index 00000000000..c2d5b4e6bc6
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-video.json
@@ -0,0 +1,188 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-video-small",
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 5
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-video-small",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-app-video-large",
+ "video": {
+ "w": 1280,
+ "h": 720,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 10,
+ "maxduration": 60,
+ "protocols": [
+ 2,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-video-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "app": {
+ "bundle": "com.prebid.app"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-app-video-small",
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 5
+ ]
+ },
+ "tagid": "app-video-small",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-app-video-large",
+ "video": {
+ "w": 1280,
+ "h": 720,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 10,
+ "maxduration": 60,
+ "protocols": [
+ 2,
+ 5,
+ 6
+ ]
+ },
+ "tagid": "app-video-large",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-app-video-small",
+ "imp-app-video-large"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-app-video-small-1",
+ "impid": "imp-app-video-small",
+ "price": 3.0,
+ "adm": "App Small Video Ad",
+ "crid": "crid-app-video-small-1",
+ "mtype": 2
+ },
+ {
+ "id": "bid-app-video-large-1",
+ "impid": "imp-app-video-large",
+ "price": 5.5,
+ "adm": "App Large Video Ad",
+ "crid": "crid-app-video-large-1",
+ "mtype": 2
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-app-video-small-1",
+ "impid": "imp-app-video-small",
+ "price": 3.0,
+ "adm": "App Small Video Ad",
+ "crid": "crid-app-video-small-1",
+ "mtype": 2
+ },
+ "type": "video"
+ },
+ {
+ "bid": {
+ "id": "bid-app-video-large-1",
+ "impid": "imp-app-video-large",
+ "price": 5.5,
+ "adm": "App Large Video Ad",
+ "crid": "crid-app-video-large-1",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/multi-route-grouping.json b/adapters/rtbstack/rtbstacktest/exemplary/multi-route-grouping.json
new file mode 100644
index 00000000000..543b99815e1
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/multi-route-grouping.json
@@ -0,0 +1,235 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-route-a-1",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=client-a&endpoint=100&ssp=200",
+ "tagId": "tag-a-1"
+ }
+ }
+ },
+ {
+ "id": "imp-route-b-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.eu-adx-admixer.rtb-stack.com/prebid?client=client-b&endpoint=300&ssp=400",
+ "tagId": "tag-b-1"
+ }
+ }
+ },
+ {
+ "id": "imp-route-a-2",
+ "banner": {
+ "format": [
+ {
+ "w": 970,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=client-a&endpoint=100&ssp=200",
+ "tagId": "tag-a-2"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=200&endpoint=100&client=client-a",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-route-a-1",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "tag-a-1",
+ "ext": {}
+ },
+ {
+ "id": "imp-route-a-2",
+ "banner": {
+ "format": [
+ {
+ "w": 970,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "tag-a-2",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-route-a-1",
+ "imp-route-a-2"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-a-1",
+ "impid": "imp-route-a-1",
+ "price": 1.25,
+ "adm": "A1
",
+ "crid": "crid-a-1",
+ "mtype": 1
+ },
+ {
+ "id": "bid-a-2",
+ "impid": "imp-route-a-2",
+ "price": 2.5,
+ "adm": "A2
",
+ "crid": "crid-a-2",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "https://eu-adx-admixer.rtb-stack.com/pbs?ssp=400&endpoint=300&client=client-b",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-route-b-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "tag-b-1",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-route-b-1"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-b-1",
+ "impid": "imp-route-b-1",
+ "price": 0.75,
+ "adm": "B1
",
+ "crid": "crid-b-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-a-1",
+ "impid": "imp-route-a-1",
+ "price": 1.25,
+ "adm": "A1
",
+ "crid": "crid-a-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-a-2",
+ "impid": "imp-route-a-2",
+ "price": 2.5,
+ "adm": "A2
",
+ "crid": "crid-a-2",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-b-1",
+ "impid": "imp-route-b-1",
+ "price": 0.75,
+ "adm": "B1
",
+ "crid": "crid-b-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/multi-route-same.json b/adapters/rtbstack/rtbstacktest/exemplary/multi-route-same.json
new file mode 100644
index 00000000000..a419ed2bccc
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/multi-route-same.json
@@ -0,0 +1,162 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-same-route",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-same-1",
+ "banner": {
+ "format": [{ "w": 728, "h": 90 }]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=client-x&endpoint=500&ssp=600",
+ "tagId": "tag-1"
+ }
+ }
+ },
+ {
+ "id": "imp-same-2",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=client-x&endpoint=500&ssp=600",
+ "tagId": "tag-2"
+ }
+ }
+ },
+ {
+ "id": "imp-same-3",
+ "banner": {
+ "format": [{ "w": 970, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=client-x&endpoint=500&ssp=600",
+ "tagId": "tag-3"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=600&endpoint=500&client=client-x",
+ "body": {
+ "id": "test-request-same-route",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-same-1",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "tagid": "tag-1",
+ "ext": {}
+ },
+ {
+ "id": "imp-same-2",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "tagid": "tag-2",
+ "ext": {}
+ },
+ {
+ "id": "imp-same-3",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "tagid": "tag-3",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": ["imp-same-1", "imp-same-2", "imp-same-3"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-same-route",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-same-1",
+ "impid": "imp-same-1",
+ "price": 1.10,
+ "adm": "1
",
+ "crid": "crid-1",
+ "mtype": 1
+ },
+ {
+ "id": "bid-same-2",
+ "impid": "imp-same-2",
+ "price": 1.20,
+ "adm": "2
",
+ "crid": "crid-2",
+ "mtype": 1
+ },
+ {
+ "id": "bid-same-3",
+ "impid": "imp-same-3",
+ "price": 1.30,
+ "adm": "3
",
+ "crid": "crid-3",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-same-1",
+ "impid": "imp-same-1",
+ "price": 1.10,
+ "adm": "1
",
+ "crid": "crid-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-same-2",
+ "impid": "imp-same-2",
+ "price": 1.20,
+ "adm": "2
",
+ "crid": "crid-2",
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-same-3",
+ "impid": "imp-same-3",
+ "price": 1.30,
+ "adm": "3
",
+ "crid": "crid-3",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/no-currency-in-response.json b/adapters/rtbstack/rtbstacktest/exemplary/no-currency-in-response.json
new file mode 100644
index 00000000000..8e3f9494605
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/no-currency-in-response.json
@@ -0,0 +1,99 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "test-tag"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "test-tag",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-banner"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-banner",
+ "price": 1.0,
+ "adm": "Banner
",
+ "crid": "crid-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "imp-banner",
+ "price": 1.0,
+ "adm": "Banner
",
+ "crid": "crid-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/optional-params.json b/adapters/rtbstack/rtbstacktest/exemplary/optional-params.json
new file mode 100644
index 00000000000..8e3f14abfd9
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/optional-params.json
@@ -0,0 +1,228 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-empty-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "empty-custom",
+ "customParams": {}
+ }
+ }
+ },
+ {
+ "id": "imp-numeric-bool",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "numeric-bool",
+ "customParams": {
+ "floor": 0.45,
+ "enabled": true,
+ "count": 3
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-nested-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 970,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "nested",
+ "customParams": {
+ "placement": {
+ "position": "top",
+ "sticky": true
+ },
+ "sizes": [
+ "970x250",
+ "728x90"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-no-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 468,
+ "h": 60
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "no-custom"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-empty-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "empty-custom",
+ "ext": {}
+ },
+ {
+ "id": "imp-numeric-bool",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "tagid": "numeric-bool",
+ "ext": {
+ "customParams": {
+ "floor": 0.45,
+ "enabled": true,
+ "count": 3
+ }
+ }
+ },
+ {
+ "id": "imp-nested-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 970,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "nested",
+ "ext": {
+ "customParams": {
+ "placement": {
+ "position": "top",
+ "sticky": true
+ },
+ "sizes": [
+ "970x250",
+ "728x90"
+ ]
+ }
+ }
+ },
+ {
+ "id": "imp-no-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 468,
+ "h": 60
+ }
+ ]
+ },
+ "tagid": "no-custom",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic",
+ "imp-empty-custom",
+ "imp-numeric-bool",
+ "imp-nested-custom",
+ "imp-no-custom"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-audio.json b/adapters/rtbstack/rtbstacktest/exemplary/site-audio.json
new file mode 100644
index 00000000000..952d026e80b
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-audio.json
@@ -0,0 +1,158 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-audio-small",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 5,
+ "maxduration": 30
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "site-audio-small",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-audio-large",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 60
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "site-audio-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-audio-small",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 5,
+ "maxduration": 30
+ },
+ "tagid": "site-audio-small",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-audio-large",
+ "audio": {
+ "mimes": [
+ "audio/mpeg",
+ "audio/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 60
+ },
+ "tagid": "site-audio-large",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-audio-small",
+ "imp-audio-large"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-site-audio-small-1",
+ "impid": "imp-audio-small",
+ "price": 0.75,
+ "adm": "",
+ "crid": "crid-site-audio-small-1",
+ "mtype": 3
+ },
+ {
+ "id": "bid-site-audio-large-1",
+ "impid": "imp-audio-large",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-site-audio-large-1",
+ "mtype": 3
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-site-audio-small-1",
+ "impid": "imp-audio-small",
+ "price": 0.75,
+ "adm": "",
+ "crid": "crid-site-audio-small-1",
+ "mtype": 3
+ },
+ "type": "audio"
+ },
+ {
+ "bid": {
+ "id": "bid-site-audio-large-1",
+ "impid": "imp-audio-large",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-site-audio-large-1",
+ "mtype": 3
+ },
+ "type": "audio"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-banner.json b/adapters/rtbstack/rtbstacktest/exemplary/site-banner.json
new file mode 100644
index 00000000000..2c0e908c6e3
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-banner.json
@@ -0,0 +1,229 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-empty-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "empty-custom",
+ "customParams": {}
+ }
+ }
+ },
+ {
+ "id": "imp-nested-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 970,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "nested",
+ "customParams": {
+ "placement": {
+ "position": "top",
+ "sticky": true
+ },
+ "sizes": [
+ "970x250",
+ "728x90"
+ ]
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-empty-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "empty-custom",
+ "ext": {}
+ },
+ {
+ "id": "imp-nested-custom",
+ "banner": {
+ "format": [
+ {
+ "w": 970,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "nested",
+ "ext": {
+ "customParams": {
+ "placement": {
+ "position": "top",
+ "sticky": true
+ },
+ "sizes": [
+ "970x250",
+ "728x90"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic",
+ "imp-empty-custom",
+ "imp-nested-custom"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-basic-1",
+ "impid": "imp-basic",
+ "price": 1.25,
+ "adm": "Banner 728x90
",
+ "crid": "crid-basic-1",
+ "mtype": 1
+ },
+ {
+ "id": "bid-empty-1",
+ "impid": "imp-empty-custom",
+ "price": 0.75,
+ "adm": "Banner 300x250
",
+ "crid": "crid-empty-1",
+ "mtype": 1
+ },
+ {
+ "id": "bid-nested-1",
+ "impid": "imp-nested-custom",
+ "price": 2.5,
+ "adm": "Banner 970x250
",
+ "crid": "crid-nested-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-basic-1",
+ "impid": "imp-basic",
+ "price": 1.25,
+ "adm": "Banner 728x90
",
+ "crid": "crid-basic-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-empty-1",
+ "impid": "imp-empty-custom",
+ "price": 0.75,
+ "adm": "Banner 300x250
",
+ "crid": "crid-empty-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-nested-1",
+ "impid": "imp-nested-custom",
+ "price": 2.5,
+ "adm": "Banner 970x250
",
+ "crid": "crid-nested-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-domain-empty-mutation.json b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-empty-mutation.json
new file mode 100644
index 00000000000..4339d77473e
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-empty-mutation.json
@@ -0,0 +1,70 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-domain-fallback.json b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-fallback.json
new file mode 100644
index 00000000000..fd198b1bc29
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-fallback.json
@@ -0,0 +1,100 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "not-a-url",
+ "domain": ""
+ },
+ "imp": [
+ {
+ "id": "imp-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "test-tag"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "not-a-url",
+ "domain": "not-a-url"
+ },
+ "imp": [
+ {
+ "id": "imp-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "test-tag",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-banner"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-banner",
+ "price": 1.0,
+ "adm": "Banner
",
+ "crid": "crid-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-1",
+ "impid": "imp-banner",
+ "price": 1.0,
+ "adm": "Banner
",
+ "crid": "crid-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-domain-mutation.json b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-mutation.json
new file mode 100644
index 00000000000..89954b712a9
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-mutation.json
@@ -0,0 +1,71 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://not-empty.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://not-empty.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-native.json b/adapters/rtbstack/rtbstacktest/exemplary/site-native.json
new file mode 100644
index 00000000000..9d4b0182030
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-native.json
@@ -0,0 +1,148 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-site-native-small",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":50}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":120,\"hmin\":120,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":100}}]}"
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "site-native-small",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-site-native-large",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":2,\"adunit\":2,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":300,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":150}}]}"
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "site-native-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "device": {
+ "ifa": "ec943cb9-61ec-460f-a925-6489c3fcc4e3"
+ },
+ "imp": [
+ {
+ "id": "imp-site-native-small",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":1,\"adunit\":1,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":50}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":120,\"hmin\":120,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":100}}]}"
+ },
+ "tagid": "site-native-small",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-site-native-large",
+ "native": {
+ "ver": "1.1",
+ "request": "{\"ver\":\"1.0\",\"layout\":2,\"adunit\":2,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":300,\"hmin\":300,\"mimes\":[\"image/jpeg\",\"image/png\"]}},{\"id\":3,\"required\":0,\"data\":{\"type\":2,\"len\":150}}]}"
+ },
+ "tagid": "site-native-large",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-site-native-small",
+ "imp-site-native-large"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-site-native-small-1",
+ "impid": "imp-site-native-small",
+ "price": 1.5,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Small Native Ad\"}}]}}",
+ "crid": "crid-site-native-small-1",
+ "mtype": 4
+ },
+ {
+ "id": "bid-site-native-large-1",
+ "impid": "imp-site-native-large",
+ "price": 2.75,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Large Native Ad\"}}]}}",
+ "crid": "crid-site-native-large-1",
+ "mtype": 4
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-site-native-small-1",
+ "impid": "imp-site-native-small",
+ "price": 1.5,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Small Native Ad\"}}]}}",
+ "crid": "crid-site-native-small-1",
+ "mtype": 4
+ },
+ "type": "native"
+ },
+ {
+ "bid": {
+ "id": "bid-site-native-large-1",
+ "impid": "imp-site-native-large",
+ "price": 2.75,
+ "adm": "{\"native\":{\"assets\":[{\"id\":1,\"title\":{\"text\":\"Large Native Ad\"}}]}}",
+ "crid": "crid-site-native-large-1",
+ "mtype": 4
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-region-eu.json b/adapters/rtbstack/rtbstacktest/exemplary/site-region-eu.json
new file mode 100644
index 00000000000..c027397c7f2
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-region-eu.json
@@ -0,0 +1,107 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-eu",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-eu-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://publisher.eu-adx-admixer.rtb-stack.com/prebid?client=a1b2c3d4-e5f6-7890-abcd-ef1234567890&endpoint=500&ssp=200",
+ "tagId": "eu-banner",
+ "customParams": {
+ "geo": "europe"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://eu-adx-admixer.rtb-stack.com/pbs?ssp=200&endpoint=500&client=a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "body": {
+ "id": "test-request-eu",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-eu-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "eu-banner",
+ "ext": {
+ "customParams": {
+ "geo": "europe"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-eu-banner"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-eu",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-eu-1",
+ "impid": "imp-eu-banner",
+ "price": 1.1,
+ "adm": "EU Banner 300x250
",
+ "crid": "crid-eu-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "EUR"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "EUR",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-eu-1",
+ "impid": "imp-eu-banner",
+ "price": 1.1,
+ "adm": "EU Banner 300x250
",
+ "crid": "crid-eu-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-region-sg.json b/adapters/rtbstack/rtbstacktest/exemplary/site-region-sg.json
new file mode 100644
index 00000000000..7c685fe00ab
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-region-sg.json
@@ -0,0 +1,113 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-sg",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-sg-video",
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://mysite.sg-adx-admixer.rtb-stack.com/prebid?client=f9e8d7c6-b5a4-3210-fedc-ba0987654321&endpoint=777&ssp=88",
+ "tagId": "sg-video",
+ "customParams": {
+ "geo": "asia"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://sg-adx-admixer.rtb-stack.com/pbs?ssp=88&endpoint=777&client=f9e8d7c6-b5a4-3210-fedc-ba0987654321",
+ "body": {
+ "id": "test-request-sg",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-sg-video",
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ]
+ },
+ "tagid": "sg-video",
+ "ext": {
+ "customParams": {
+ "geo": "asia"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-sg-video"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-sg",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-sg-1",
+ "impid": "imp-sg-video",
+ "price": 4.5,
+ "adm": "SG Video Ad",
+ "crid": "crid-sg-1",
+ "mtype": 2
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-sg-1",
+ "impid": "imp-sg-video",
+ "price": 4.5,
+ "adm": "SG Video Ad",
+ "crid": "crid-sg-1",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-video.json b/adapters/rtbstack/rtbstacktest/exemplary/site-video.json
new file mode 100644
index 00000000000..6d3f1aeefe0
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-video.json
@@ -0,0 +1,184 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-video-small",
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 5
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "video-small",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-video-large",
+ "video": {
+ "w": 1280,
+ "h": 720,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 10,
+ "maxduration": 60,
+ "protocols": [
+ 2,
+ 5,
+ 6
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "video-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-video-small",
+ "video": {
+ "w": 640,
+ "h": 360,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 5,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 5
+ ]
+ },
+ "tagid": "video-small",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-video-large",
+ "video": {
+ "w": 1280,
+ "h": 720,
+ "mimes": [
+ "video/mp4",
+ "video/webm"
+ ],
+ "minduration": 10,
+ "maxduration": 60,
+ "protocols": [
+ 2,
+ 5,
+ 6
+ ]
+ },
+ "tagid": "video-large",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-video-small",
+ "imp-video-large"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-video-small-1",
+ "impid": "imp-video-small",
+ "price": 3.0,
+ "adm": "Small Video Ad",
+ "crid": "crid-video-small-1",
+ "mtype": 2
+ },
+ {
+ "id": "bid-video-large-1",
+ "impid": "imp-video-large",
+ "price": 5.5,
+ "adm": "Large Video Ad",
+ "crid": "crid-video-large-1",
+ "mtype": 2
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-video-small-1",
+ "impid": "imp-video-small",
+ "price": 3.0,
+ "adm": "Small Video Ad",
+ "crid": "crid-video-small-1",
+ "mtype": 2
+ },
+ "type": "video"
+ },
+ {
+ "bid": {
+ "id": "bid-video-large-1",
+ "impid": "imp-video-large",
+ "price": 5.5,
+ "adm": "Large Video Ad",
+ "crid": "crid-video-large-1",
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/bad-bidder-ext.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-bidder-ext.json
new file mode 100644
index 00000000000..5d3c2f45095
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-bidder-ext.json
@@ -0,0 +1,27 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-bad-bidder-ext",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-bad-ext",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": "not-an-object"
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Wrong RTBStack bidder ext",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/bad-imp.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-imp.json
new file mode 100644
index 00000000000..7fb55166c3f
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-imp.json
@@ -0,0 +1,29 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-error",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-invalid",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": "invalid-json"
+ }
+ ]
+ },
+
+ "httpCalls": [],
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "expect { or n, but found \"",
+ "comparison": "literal"
+ }
+ ],
+
+ "expectedBidResponses": []
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/bad-mtype.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-mtype.json
new file mode 100644
index 00000000000..2b79830d114
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-mtype.json
@@ -0,0 +1,94 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "test-tag"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-banner",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "test-tag",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-banner"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-1",
+ "impid": "imp-banner",
+ "price": 1.0,
+ "adm": "Banner
",
+ "crid": "crid-1",
+ "mtype": 0
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": []
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "unsupported MType 0 for bid imp-banner",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/bad-response.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-response.json
new file mode 100644
index 00000000000..2901ee1359a
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-response.json
@@ -0,0 +1,69 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "test-banner",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "test-banner",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": "{\"id\"data.lost"
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "expect { or n, but found \"",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/bad-route-invalid-region.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-route-invalid-region.json
new file mode 100644
index 00000000000..156148b2cd9
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-route-invalid-region.json
@@ -0,0 +1,30 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-bad-region",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-bad-region",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://publisher.xx-adx-admixer.rtb-stack.com/prebid?client=abc&endpoint=1&ssp=2",
+ "tagId": "test-tag"
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "unable to extract valid region from route URL hostname",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/bad-route-missing-params.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-route-missing-params.json
new file mode 100644
index 00000000000..ad6a6a463dc
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-route-missing-params.json
@@ -0,0 +1,30 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-missing-params",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-missing-params",
+ "banner": {
+ "format": [{ "w": 300, "h": 250 }]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://publisher.us-adx-admixer.rtb-stack.com/prebid",
+ "tagId": "test-tag"
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "route URL must contain client, endpoint, and ssp query parameters",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/multi-route-partial-failure.json b/adapters/rtbstack/rtbstacktest/supplemental/multi-route-partial-failure.json
new file mode 100644
index 00000000000..ebbf7c392d1
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/multi-route-partial-failure.json
@@ -0,0 +1,123 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-mixed-routes",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-good",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://publisher.us-adx-admixer.rtb-stack.com/prebid?client=client-good&endpoint=10&ssp=20",
+ "tagId": "good-tag"
+ }
+ }
+ },
+ {
+ "id": "imp-bad-region",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://publisher.xx-adx-admixer.rtb-stack.com/prebid?client=client-bad&endpoint=99&ssp=88",
+ "tagId": "bad-tag"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=20&endpoint=10&client=client-good",
+ "body": {
+ "id": "test-request-mixed-routes",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-good",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "good-tag",
+ "ext": {}
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-good"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-mixed-routes",
+ "seatbid": [
+ {
+ "seat": "rtbstack",
+ "bid": [
+ {
+ "id": "bid-good-1",
+ "impid": "imp-good",
+ "price": 1.0,
+ "adm": "good
",
+ "crid": "crid-good-1",
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "bid-good-1",
+ "impid": "imp-good",
+ "price": 1.0,
+ "adm": "good
",
+ "crid": "crid-good-1",
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "unable to extract valid region from route URL hostname",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/no-imps.json b/adapters/rtbstack/rtbstacktest/supplemental/no-imps.json
new file mode 100644
index 00000000000..74130ada649
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/no-imps.json
@@ -0,0 +1,17 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-no-imps",
+ "site": {
+ "page": "https://example.com",
+ "domain": "example.com"
+ },
+ "imp": []
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "No impressions in request",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/status-204.json b/adapters/rtbstack/rtbstacktest/supplemental/status-204.json
new file mode 100644
index 00000000000..49d97f6c639
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/status-204.json
@@ -0,0 +1,71 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/status-400.json b/adapters/rtbstack/rtbstacktest/supplemental/status-400.json
new file mode 100644
index 00000000000..4b8f01fd8a5
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/status-400.json
@@ -0,0 +1,77 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "basic",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic"
+ ]
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/supplemental/two-imps-one-is-bad.json b/adapters/rtbstack/rtbstacktest/supplemental/two-imps-one-is-bad.json
new file mode 100644
index 00000000000..04c1d35d984
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/two-imps-one-is-bad.json
@@ -0,0 +1,89 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-partial",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-valid",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "route": "https://testsite.us-adx-admixer.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "valid-tag",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ },
+ {
+ "id": "imp-invalid",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": "invalid-json"
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://us-adx-admixer.rtb-stack.com/pbs?ssp=145&endpoint=309&client=c4527281-5aa5-4c8e-bc53-a80bb3f99470",
+ "body": {
+ "id": "test-request-partial",
+ "site": {
+ "page": "https://example.com",
+ "domain": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-valid",
+ "banner": {
+ "format": [
+ {
+ "w": 728,
+ "h": 90
+ }
+ ]
+ },
+ "tagid": "valid-tag",
+ "ext": {
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-valid"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "expect { or n, but found \"",
+ "comparison": "literal"
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 630fbe348b9..a9a93f172dc 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -199,6 +199,7 @@ import (
"github.com/prebid/prebid-server/v4/adapters/rise"
"github.com/prebid/prebid-server/v4/adapters/roulax"
"github.com/prebid/prebid-server/v4/adapters/rtbhouse"
+ "github.com/prebid/prebid-server/v4/adapters/rtbstack"
"github.com/prebid/prebid-server/v4/adapters/rubicon"
salunamedia "github.com/prebid/prebid-server/v4/adapters/sa_lunamedia"
"github.com/prebid/prebid-server/v4/adapters/seedingAlliance"
@@ -469,6 +470,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderRise: rise.Builder,
openrtb_ext.BidderRoulax: roulax.Builder,
openrtb_ext.BidderRTBHouse: rtbhouse.Builder,
+ openrtb_ext.BidderRTBStack: rtbstack.Builder,
openrtb_ext.BidderRubicon: rubicon.Builder,
openrtb_ext.BidderSeedingAlliance: seedingAlliance.Builder,
openrtb_ext.BidderSeedtag: seedtag.Builder,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 1b61ff724e2..fb58b51b58b 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -217,6 +217,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderRise,
BidderRoulax,
BidderRTBHouse,
+ BidderRTBStack,
BidderRubicon,
BidderSeedingAlliance,
BidderSeedtag,
@@ -591,6 +592,7 @@ const (
BidderRise BidderName = "rise"
BidderRoulax BidderName = "roulax"
BidderRTBHouse BidderName = "rtbhouse"
+ BidderRTBStack BidderName = "rtbstack"
BidderRubicon BidderName = "rubicon"
BidderSeedingAlliance BidderName = "seedingAlliance"
BidderSeedtag BidderName = "seedtag"
diff --git a/openrtb_ext/imp_rtbstack.go b/openrtb_ext/imp_rtbstack.go
new file mode 100644
index 00000000000..d98e1b69bdd
--- /dev/null
+++ b/openrtb_ext/imp_rtbstack.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ExtImpRTBStack struct {
+ Route string `json:"route"`
+ TagId string `json:"tagId"`
+ CustomParams map[string]interface{} `json:"customParams,omitempty"`
+}
diff --git a/static/bidder-info/rtbstack.yaml b/static/bidder-info/rtbstack.yaml
new file mode 100644
index 00000000000..34f02f0e973
--- /dev/null
+++ b/static/bidder-info/rtbstack.yaml
@@ -0,0 +1,19 @@
+endpoint: "https://{{.Region}}-adx-admixer.rtb-stack.com/pbs?ssp={{.SspID}}&endpoint={{.ZoneID}}&client={{.PartnerId}}"
+maintainer:
+ email: "prebid@admixer.net"
+gvlVendorID: 511
+openrtb:
+ version: "2.6"
+capabilities:
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ - audio
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ - audio
\ No newline at end of file
diff --git a/static/bidder-params/rtbstack.json b/static/bidder-params/rtbstack.json
new file mode 100644
index 00000000000..b5b90aefd06
--- /dev/null
+++ b/static/bidder-params/rtbstack.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "RTBStack Adapter Params",
+ "description": "A schema which validates params accepted by the RTBStack adapter",
+
+ "type": "object",
+ "properties": {
+ "route": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Full route URL used to derive RTBStack endpoint (contains region, client, endpoint, ssp)."
+ },
+ "tagId": {
+ "type": "string",
+ "description": "AdUnit tag id."
+ },
+ "customParams": {
+ "type": "object",
+ "description": "Custom values for targeting."
+ }
+ },
+
+ "required": ["route", "tagId"]
+}
\ No newline at end of file