diff --git a/adapters/rtbstack/rtbstack.go b/adapters/rtbstack/rtbstack.go
new file mode 100644
index 00000000000..9baa2513b65
--- /dev/null
+++ b/adapters/rtbstack/rtbstack.go
@@ -0,0 +1,216 @@
+package rtbstack
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "text/template"
+
+ "github.com/prebid/openrtb/v20/openrtb2"
+ "github.com/prebid/prebid-server/v3/adapters"
+ "github.com/prebid/prebid-server/v3/config"
+ "github.com/prebid/prebid-server/v3/errortypes"
+ "github.com/prebid/prebid-server/v3/macros"
+ "github.com/prebid/prebid-server/v3/openrtb_ext"
+ "github.com/prebid/prebid-server/v3/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 {
+ TagId string `json:"tagid"`
+ 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
+ var validImps []*impCtx
+
+ for _, imp := range request.Imp {
+ ext, err := preprocessImp(&imp)
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ validImps = append(validImps, &impCtx{
+ imp: imp,
+ rtbStackExt: ext,
+ })
+ }
+
+ if len(validImps) == 0 {
+ return nil, errs
+ }
+
+ request.Imp = nil
+ for _, v := range validImps {
+ request.Imp = append(request.Imp, v.imp)
+ }
+
+ endpoint, err := a.buildEndpointURL(validImps[0].rtbStackExt)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ var newRequest openrtb2.BidRequest
+ newRequest = *request
+
+ if request.Site != nil && request.Site.Domain == "" {
+ newSite := *request.Site
+ newSite.Domain = request.Site.Page
+ newRequest.Site = &newSite
+ }
+
+ reqJSON, err := json.Marshal(newRequest)
+ if err != nil {
+ return nil, []error{&errortypes.BadInput{
+ Message: "Error parsing reqJSON object",
+ }}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: endpoint,
+ Body: reqJSON,
+ Headers: headers,
+ ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
+ }}, []error{}
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{fmt.Errorf("Unexpected status code: %d", response.StatusCode)}
+ }
+
+ var bidResp openrtb2.BidResponse
+ if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ if len(bidResp.SeatBid) == 0 || len(bidResp.SeatBid[0].Bid) == 0 {
+ return nil, nil
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+
+ for _, sb := range bidResp.SeatBid {
+ for i := 0; i < len(sb.Bid); i++ {
+ bid := sb.Bid[i]
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp),
+ })
+
+ }
+ }
+ return bidResponse, []error{}
+}
+
+func (a *adapter) buildEndpointURL(ext *openrtb_ext.ExtImpRTBStack) (string, error) {
+ // Normalize host to avoid accidental protocol prefixes and domains accidentally included
+ host := ext.Host
+ host = strings.TrimPrefix(host, "http://")
+ host = strings.TrimPrefix(host, "https://")
+
+ endpointParams := macros.EndpointTemplateParams{Host: host}
+ baseURL, err := macros.ResolveMacros(a.endpoint, endpointParams)
+ if err != nil {
+ return "", fmt.Errorf("unable to resolve endpoint: %v", err)
+ }
+
+ if ext.Query == "" {
+ return baseURL, nil
+ }
+
+ if strings.HasPrefix(ext.Query, "/") {
+ return baseURL + ext.Query, nil
+ }
+
+ return baseURL + "/" + ext.Query, nil
+}
+
+func preprocessImp(
+ imp *openrtb2.Imp,
+) (*openrtb_ext.ExtImpRTBStack, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{Message: err.Error()}
+ }
+
+ var impExt openrtb_ext.ExtImpRTBStack
+ if err := jsonutil.Unmarshal(bidderExt.Bidder, &impExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: "Wrong RTBStack bidder ext",
+ }
+ }
+
+ imp.TagID = impExt.TagId
+
+ // create new imp->ext without odd params
+ newExt := extImpRTBStack{
+ TagId: impExt.TagId,
+ CustomParams: impExt.CustomParams,
+ }
+
+ // simplify content from imp->ext->bidder to imp->ext
+ newImpExtForRTBStack, err := json.Marshal(newExt)
+ if err != nil {
+ return nil, &errortypes.BadInput{Message: err.Error()}
+ }
+ imp.Ext = newImpExtForRTBStack
+
+ return &impExt, nil
+}
+
+func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
+ for _, imp := range imps {
+ if imp.ID == impID {
+ if imp.Banner != nil {
+ return openrtb_ext.BidTypeBanner
+ } else if imp.Video != nil {
+ return openrtb_ext.BidTypeVideo
+ } else if imp.Native != nil {
+ return openrtb_ext.BidTypeNative
+ } else if imp.Audio != nil {
+ return openrtb_ext.BidTypeAudio
+ }
+ }
+ }
+ return openrtb_ext.BidTypeBanner
+}
diff --git a/adapters/rtbstack/rtbstack_test.go b/adapters/rtbstack/rtbstack_test.go
new file mode 100644
index 00000000000..7f31f0e562f
--- /dev/null
+++ b/adapters/rtbstack/rtbstack_test.go
@@ -0,0 +1,19 @@
+package rtbstack
+
+import (
+ "github.com/prebid/prebid-server/v3/adapters/adapterstest"
+ "github.com/prebid/prebid-server/v3/config"
+ "github.com/prebid/prebid-server/v3/openrtb_ext"
+ "testing"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderRTBStack, config.Adapter{
+ Endpoint: "https://{{.Host}}.rtb-stack.com"}, 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)
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/app-audio.json b/adapters/rtbstack/rtbstacktest/exemplary/app-audio.json
new file mode 100644
index 00000000000..63865362e96
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-audio.json
@@ -0,0 +1,150 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-audio-long",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "app-audio-short",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ },
+ {
+ "id": "imp-app-audio-long",
+ "audio": {
+ "mimes": ["audio/mpeg", "audio/mp4"],
+ "minduration": 15,
+ "maxduration": 60
+ },
+ "tagid": "app-audio-long",
+ "ext": {
+ "tagid": "app-audio-long"
+ }
+ }
+ ]
+ },
+ "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"
+ },
+ {
+ "id": "bid-app-audio-long-1",
+ "impid": "imp-app-audio-long",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-app-audio-long-1"
+ }
+ ]
+ }
+ ],
+ "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"
+ },
+ "type": "audio"
+ },
+ {
+ "bid": {
+ "id": "bid-app-audio-long-1",
+ "impid": "imp-app-audio-long",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-app-audio-long-1"
+ },
+ "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..9799eafa2ec
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-banner.json
@@ -0,0 +1,130 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "app-basic",
+ "customParams": { "foo": "bar" }
+ }
+ },
+ {
+ "id": "imp-app-large",
+ "banner": { "format": [{ "w": 728, "h": 90 }] },
+ "tagid": "app-large",
+ "ext": { "tagid": "app-large" }
+ }
+ ]
+ },
+ "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"
+ },
+ {
+ "id": "bid-app-large-1",
+ "impid": "imp-app-large",
+ "price": 1.50,
+ "adm": "App Banner 728x90
",
+ "crid": "crid-app-large-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"
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-app-large-1",
+ "impid": "imp-app-large",
+ "price": 1.50,
+ "adm": "App Banner 728x90
",
+ "crid": "crid-app-large-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..ffb9c9ca44c
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-native.json
@@ -0,0 +1,142 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-native-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "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}}]}"
+ },
+ "tagid": "app-native-large",
+ "ext": { "tagid": "app-native-large" }
+ }
+ ]
+ },
+ "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"
+ },
+ {
+ "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"
+ }
+ ]
+ }
+ ],
+ "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"
+ },
+ "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"
+ },
+ "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..a050b608c44
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/app-video.json
@@ -0,0 +1,158 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "app-video-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "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]
+ },
+ "tagid": "app-video-large",
+ "ext": { "tagid": "app-video-large" }
+ }
+ ]
+ },
+ "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"
+ },
+ {
+ "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"
+ }
+ ]
+ }
+ ],
+ "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"
+ },
+ "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"
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/optional-params.json b/adapters/rtbstack/rtbstacktest/exemplary/optional-params.json
new file mode 100644
index 00000000000..53de9ed9732
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/optional-params.json
@@ -0,0 +1,182 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "no-custom"
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "basic",
+ "customParams": { "foo": "bar" }
+ }
+ },
+
+ {
+ "id": "imp-empty-custom",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "tagid": "empty-custom",
+ "ext": {
+ "tagid": "empty-custom"
+ }
+ },
+
+ {
+ "id": "imp-numeric-bool",
+ "banner": { "format": [{ "w": 320, "h": 50 }] },
+ "tagid": "numeric-bool",
+ "ext": {
+ "tagid": "numeric-bool",
+ "customParams": {
+ "floor": 0.45,
+ "enabled": true,
+ "count": 3
+ }
+ }
+ },
+
+ {
+ "id": "imp-nested-custom",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "tagid": "nested",
+ "ext": {
+ "tagid": "nested",
+ "customParams": {
+ "placement": {
+ "position": "top",
+ "sticky": true
+ },
+ "sizes": ["970x250", "728x90"]
+ }
+ }
+ },
+
+ {
+ "id": "imp-no-custom",
+ "banner": { "format": [{ "w": 468, "h": 60 }] },
+ "tagid": "no-custom",
+ "ext": {
+ "tagid": "no-custom"
+ }
+ }
+ ]
+ },
+ "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..a3b4a18c177
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-audio.json
@@ -0,0 +1,142 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "site-audio-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "site-audio-small",
+ "customParams": { "foo": "bar" }
+ }
+ },
+ {
+ "id": "imp-audio-large",
+ "audio": {
+ "mimes": ["audio/mpeg", "audio/mp4"],
+ "minduration": 15,
+ "maxduration": 60
+ },
+ "tagid": "site-audio-large",
+ "ext": { "tagid": "site-audio-large" }
+ }
+ ]
+ },
+ "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"
+ },
+ {
+ "id": "bid-site-audio-large-1",
+ "impid": "imp-audio-large",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-site-audio-large-1"
+ }
+ ]
+ }
+ ],
+ "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"
+ },
+ "type": "audio"
+ },
+ {
+ "bid": {
+ "id": "bid-site-audio-large-1",
+ "impid": "imp-audio-large",
+ "price": 1.25,
+ "adm": "",
+ "crid": "crid-site-audio-large-1"
+ },
+ "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..c7c753c6cbc
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-banner.json
@@ -0,0 +1,184 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "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://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "basic",
+ "customParams": { "foo": "bar" }
+ }
+ },
+ {
+ "id": "imp-empty-custom",
+ "banner": { "format": [{ "w": 300, "h": 250 }] },
+ "tagid": "empty-custom",
+ "ext": {
+ "tagid": "empty-custom"
+ }
+ },
+ {
+ "id": "imp-nested-custom",
+ "banner": { "format": [{ "w": 970, "h": 250 }] },
+ "tagid": "nested",
+ "ext": {
+ "tagid": "nested",
+ "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"
+ },
+ {
+ "id": "bid-empty-1",
+ "impid": "imp-empty-custom",
+ "price": 0.75,
+ "adm": "Banner 300x250
",
+ "crid": "crid-empty-1"
+ },
+ {
+ "id": "bid-nested-1",
+ "impid": "imp-nested-custom",
+ "price": 2.50,
+ "adm": "Banner 970x250
",
+ "crid": "crid-nested-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"
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-empty-1",
+ "impid": "imp-empty-custom",
+ "price": 0.75,
+ "adm": "Banner 300x250
",
+ "crid": "crid-empty-1"
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "bid-nested-1",
+ "impid": "imp-nested-custom",
+ "price": 2.50,
+ "adm": "Banner 970x250
",
+ "crid": "crid-nested-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..a878780e27a
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-empty-mutation.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "site": {
+ "page": "https://example.com"
+ },
+ "imp": [
+ {
+ "id": "imp-basic",
+ "banner": {
+ "format": [{ "w": 728, "h": 90 }]
+ },
+ "ext": {
+ "bidder": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "basic",
+ "customParams": { "foo": "bar" }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "imp-basic"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+
+ "expectedBidResponses": []
+}
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..61d5ac611eb
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-domain-mutation.json
@@ -0,0 +1,61 @@
+{
+ "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "basic",
+ "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..ff5f05afdb7
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-native.json
@@ -0,0 +1,144 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "site-native-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "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}}]}"
+ },
+ "tagid": "site-native-large",
+ "ext": { "tagid": "site-native-large" }
+ }
+ ]
+ },
+ "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"
+ },
+ {
+ "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"
+ }
+ ]
+ }
+ ],
+ "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"
+ },
+ "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"
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/rtbstack/rtbstacktest/exemplary/site-video.json b/adapters/rtbstack/rtbstacktest/exemplary/site-video.json
new file mode 100644
index 00000000000..b6baf30f0bb
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/exemplary/site-video.json
@@ -0,0 +1,154 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "video-large",
+ "customParams": {}
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "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]
+ },
+ "tagid": "video-large",
+ "ext": { "tagid": "video-large" }
+ }
+ ]
+ },
+ "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.00,
+ "adm": "Small Video Ad",
+ "crid": "crid-video-small-1"
+ },
+ {
+ "id": "bid-video-large-1",
+ "impid": "imp-video-large",
+ "price": 5.50,
+ "adm": "Large Video Ad",
+ "crid": "crid-video-large-1"
+ }
+ ]
+ }
+ ],
+ "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"
+ },
+ "type": "video"
+ },
+ {
+ "bid": {
+ "id": "bid-video-large-1",
+ "impid": "imp-video-large",
+ "price": 5.5,
+ "adm": "Large Video Ad",
+ "crid": "crid-video-large-1"
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
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-response.json b/adapters/rtbstack/rtbstacktest/supplemental/bad-response.json
new file mode 100644
index 00000000000..2e2c515f583
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/bad-response.json
@@ -0,0 +1,71 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "test-banner",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "tagid": "test-banner",
+ "ext": {
+ "tagid": "test-banner",
+ "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/status-204.json b/adapters/rtbstack/rtbstacktest/supplemental/status-204.json
new file mode 100644
index 00000000000..8b59c7bf474
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/status-204.json
@@ -0,0 +1,59 @@
+{
+ "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "basic",
+ "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..a46aa75c8f7
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/status-400.json
@@ -0,0 +1,65 @@
+{
+ "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": {
+ "host": "test",
+ "query": "prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "tagId": "basic",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ }
+ ]
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "basic",
+ "customParams": { "foo": "bar" }
+ }
+ }
+ ]
+ },
+ "impIDs": ["imp-basic"]
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400",
+ "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..28d53b71f87
--- /dev/null
+++ b/adapters/rtbstack/rtbstacktest/supplemental/two-imps-one-is-bad.json
@@ -0,0 +1,70 @@
+{
+ "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": {
+ "host": "test",
+ "query": "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://test.rtb-stack.com/prebid?client=c4527281-5aa5-4c8e-bc53-a80bb3f99470&endpoint=309&ssp=145",
+ "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": {
+ "tagid": "valid-tag",
+ "customParams": {
+ "foo": "bar"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": ["imp-valid"]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+
+ "expectedMakeRequestsErrors": [],
+
+ "expectedBidResponses": []
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 9a2aaa0bd21..24031ebbc25 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -198,6 +198,7 @@ import (
"github.com/prebid/prebid-server/v3/adapters/rise"
"github.com/prebid/prebid-server/v3/adapters/roulax"
"github.com/prebid/prebid-server/v3/adapters/rtbhouse"
+ "github.com/prebid/prebid-server/v3/adapters/rtbstack"
"github.com/prebid/prebid-server/v3/adapters/rubicon"
salunamedia "github.com/prebid/prebid-server/v3/adapters/sa_lunamedia"
"github.com/prebid/prebid-server/v3/adapters/seedingAlliance"
@@ -528,5 +529,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder,
openrtb_ext.BidderZetaGlobalSsp: zeta_global_ssp.Builder,
openrtb_ext.BidderZmaticoo: zmaticoo.Builder,
+ openrtb_ext.BidderRTBStack: rtbstack.Builder,
}
}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 8cd543c1f16..97c2de71d38 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -216,6 +216,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderRise,
BidderRoulax,
BidderRTBHouse,
+ BidderRTBStack,
BidderRubicon,
BidderSeedingAlliance,
BidderSeedtag,
@@ -589,6 +590,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..78a87ebe345
--- /dev/null
+++ b/openrtb_ext/imp_rtbstack.go
@@ -0,0 +1,8 @@
+package openrtb_ext
+
+type ExtImpRTBStack struct {
+ Host string `json:"host,omitempty"`
+ Query string `json:"query,omitempty"`
+ TagId string `json:"tagid,omitempty"`
+ 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..af397e5c872
--- /dev/null
+++ b/static/bidder-info/rtbstack.yaml
@@ -0,0 +1,17 @@
+endpoint: "https://{{.Host}}.rtb-stack.com"
+maintainer:
+ email: "prebid@admixer.net"
+gvlVendorID: 511
+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..30d1ca0cf3a
--- /dev/null
+++ b/static/bidder-params/rtbstack.json
@@ -0,0 +1,27 @@
+{
+ "$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": {
+ "host": {
+ "type": "string",
+ "description": "Subdomain host used for RTBStack requests."
+ },
+ "query": {
+ "type": "string",
+ "description": "Path and query string appended to the RTBStack endpoint."
+ },
+ "tagId": {
+ "type": "string",
+ "description": "AdUnit tag id."
+ },
+ "customParams": {
+ "type": "object",
+ "description": "Custom values for targeting."
+ }
+ },
+
+ "required": ["host", "query", "tagId"]
+}
\ No newline at end of file