Repository: prebid/prebid-server
Pull request #2753 (2023-05-05 14:34:15)

New adapter: Motorik
Hey, here is a new adapter
Link to doc - https://github.com/prebid/prebid.github.io/pull/4544

motorik-io: @onkarvhanumante please take a look to all fixes that have been maed
onkarvhanumante: > @motorik-io Thanks for keeping patience with us for code reviews. Added few suggestions

@motorik-io  need your assistance in addressing the left over comments
onkarvhanumante: @motorik-io  friendly reminder to address PR changes
motorik-io: @gargcreation1992 please take a look at the new updates
onkarvhanumante: @motorik-io thanks for keeping patience with PR review.

PR tests are failing with following error:
```
# github.com/prebid/prebid-server/adapters/motorik
Error: adapters/motorik/motorik.go:116:18: undefined: httputil.CheckResponseStatusCodeForErrors
Error: adapters/motorik/motorik.go:124:14: undefined: httputil.IsResponseStatusCodeNoContent
```

https://github.com/prebid/prebid-server/pull/2788 moved above methods from httputil to adapters package. Requesting to merge master on PR to avail new master changes and update code to use adapters.CheckResponseStatusCodeForErrors and adapters.IsResponseStatusCodeNoContent



onkarvhanumante: @motorik-io  file changed count has increased 233 after https://github.com/prebid/prebid-server/pull/2753/commits/aa7789937b65989c6af3f6d8c765a8bb7c192786.
motorik-io: @onkarvhanumante yes, i see, let me fix this

Patch:
diff --git a/adapters/motorik/motorik.go b/adapters/motorik/motorik.go
new file mode 100644
index 00000000..c804c095
--- /dev/null
+++ b/adapters/motorik/motorik.go
@@ -0,0 +1,175 @@
+package motorik
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint *template.Template
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: template,
+	}
+	return bidder, nil
+}
+
+func getHeaders(request *openrtb2.BidRequest) http.Header {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	headers.Add("X-Openrtb-Version", "2.5")
+
+	if request.Device != nil {
+		if len(request.Device.UA) > 0 {
+			headers.Add("User-Agent", request.Device.UA)
+		}
+
+		if len(request.Device.IPv6) > 0 {
+			headers.Add("X-Forwarded-For", request.Device.IPv6)
+		}
+
+		if len(request.Device.IP) > 0 {
+			headers.Add("X-Forwarded-For", request.Device.IP)
+		}
+	}
+
+	return headers
+}
+
+func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) {
+	motorikExt, err := getImpressionExt(&openRTBRequest.Imp[0])
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	openRTBRequest.Imp[0].Ext = nil
+
+	url, err := a.buildEndpointURL(motorikExt)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	reqJSON, err := json.Marshal(openRTBRequest)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	return []*adapters.RequestData{{
+		Method:  http.MethodPost,
+		Body:    reqJSON,
+		Uri:     url,
+		Headers: getHeaders(openRTBRequest),
+	}}, nil
+}
+
+func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtMotorik, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error parsing motorikExt - " + err.Error(),
+		}
+	}
+	var motorikExt openrtb_ext.ExtMotorik
+	if err := json.Unmarshal(bidderExt.Bidder, &motorikExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error parsing bidderExt - " + err.Error(),
+		}
+	}
+
+	return &motorikExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtMotorik) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{AccountID: params.AccountID, SourceId: params.PlacementID}
+	return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+func checkResponseStatusCodes(response *adapters.ResponseData) error {
+	if response.StatusCode == http.StatusServiceUnavailable {
+		return &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Something went wrong Status Code: [ %d ] ", response.StatusCode),
+		}
+	}
+
+	return adapters.CheckResponseStatusCodeForErrors(response)
+}
+
+func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) {
+	if adapters.IsResponseStatusCodeNoContent(bidderRawResponse) {
+		return nil, nil
+	}
+
+	httpStatusError := checkResponseStatusCodes(bidderRawResponse)
+	if httpStatusError != nil {
+		return nil, []error{httpStatusError}
+	}
+
+	responseBody := bidderRawResponse.Body
+	var bidResp openrtb2.BidResponse
+	if err := json.Unmarshal(responseBody, &bidResp); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Bad Server Response",
+		}}
+	}
+
+	if len(bidResp.SeatBid) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Empty SeatBid array",
+		}}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+	var bidsArray []*adapters.TypedBid
+
+	for _, sb := range bidResp.SeatBid {
+		for idx, bid := range sb.Bid {
+			bidType, err := getMediaTypeForImp(bid.ImpID, openRTBRequest.Imp)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			bidsArray = append(bidsArray, &adapters.TypedBid{
+				Bid:     &sb.Bid[idx],
+				BidType: bidType,
+			})
+		}
+	}
+
+	bidResponse.Bids = bidsArray
+	return bidResponse, nil
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
+	for _, imp := range imps {
+		if imp.ID == impId {
+			if imp.Banner != nil {
+				return openrtb_ext.BidTypeBanner, nil
+			} else if imp.Video != nil {
+				return openrtb_ext.BidTypeVideo, nil
+			} else if imp.Native != nil {
+				return openrtb_ext.BidTypeNative, nil
+			}
+			break
+		}
+	}
+	return "", &errortypes.BadInput{
+		Message: fmt.Sprintf("Failed to find impression \"%s\"", impId),
+	}
+}
diff --git a/endpoints/openrtb2/sample-requests/hooks/auction_reject_with_error.json b/endpoints/openrtb2/sample-requests/hooks/auction_reject_with_error.json
new file mode 100644
index 00000000..5ca617a4
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/hooks/auction_reject_with_error.json
@@ -0,0 +1,87 @@
+{
+  "description": "Auction request",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 0.00}
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "test": 1,
+    "ext": {
+      "prebid": {
+        "trace": "verbose"
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "bidid": "test bid id",
+    "nbr": 123,
+    "ext": {
+      "prebid": {
+        "modules": {
+          "errors": {
+            "foobar": {
+              "foo": [
+                "dummy",
+                "Module foobar (hook: foo) rejected request with code 123 at entrypoint stage"
+              ]
+            }
+          },
+          "trace": {
+            "stages": [
+              {
+                "stage": "entrypoint",
+                "outcomes": [
+                  {
+                    "entity": "http-request",
+                    "groups": [
+                      {
+                        "invocation_results": [
+                          {
+                            "analytics_tags": {},
+                            "hook_id": {
+                              "module_code": "foobar",
+                              "hook_impl_code": "foo"
+                            },
+                            "status": "execution_failure",
+                            "action": "reject",
+                            "message": ""
+                          }
+                        ]
+                      }
+                    ]
+                  }
+                ]
+              }
+            ]
+          }
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 7ad3afe4..a9f54710 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -113,6 +113,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/mgid"
 	"github.com/prebid/prebid-server/adapters/mobfoxpb"
 	"github.com/prebid/prebid-server/adapters/mobilefuse"
+	"github.com/prebid/prebid-server/adapters/motorik"
 	"github.com/prebid/prebid-server/adapters/nanointeractive"
 	"github.com/prebid/prebid-server/adapters/nextmillennium"
 	"github.com/prebid/prebid-server/adapters/ninthdecimal"
@@ -267,7 +268,6 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderGamoshi:           gamoshi.Builder,
 		openrtb_ext.BidderGlobalsun:         globalsun.Builder,
 		openrtb_ext.BidderGrid:              grid.Builder,
-		openrtb_ext.BidderGroupm:            pubmatic.Builder,
 		openrtb_ext.BidderGumGum:            gumgum.Builder,
 		openrtb_ext.BidderHuaweiAds:         huaweiads.Builder,
 		openrtb_ext.BidderIionads:           limelightDigital.Builder,
@@ -300,6 +300,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderMgid:              mgid.Builder,
 		openrtb_ext.BidderMobfoxpb:          mobfoxpb.Builder,
 		openrtb_ext.BidderMobileFuse:        mobilefuse.Builder,
+		openrtb_ext.BidderMotorik:           motorik.Builder,
 		openrtb_ext.BidderNanoInteractive:   nanointeractive.Builder,
 		openrtb_ext.BidderNextMillennium:    nextmillennium.Builder,
 		openrtb_ext.BidderNinthDecimal:      ninthdecimal.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index 056dbf35..4876cef7 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -91,6 +91,7 @@ func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string
 		"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 		"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 		"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
+		"groupm":         `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
 	}
 
 	for name, info := range infos {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 8361fa27..04ffd79e 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -26,6 +26,7 @@ import (
 	"github.com/prebid/prebid-server/floors"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
+	"github.com/prebid/prebid-server/macros"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/prebid_cache_client"
@@ -76,6 +77,7 @@ type exchange struct {
 	server                   config.Server
 	bidValidationEnforcement config.Validations
 	requestSplitter          requestSplitter
+	macroReplacer            macros.Replacer
 	floor                    config.PriceFloors
 }
 
@@ -125,7 +127,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool {
 	return rand.Intn(100) < 50
 }
 
-func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer) Exchange {
+func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gdprPermsBuilder gdpr.PermissionsBuilder, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher, adsCertSigner adscert.Signer, macroReplacer macros.Replacer) Exchange {
 	bidderToSyncerKey := map[string]string{}
 	for bidder, syncer := range syncersByBidder {
 		bidderToSyncerKey[bidder] = syncer.Key()
@@ -169,6 +171,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid
 		server:                   config.Server{ExternalUrl: cfg.ExternalURL, GvlID: cfg.GDPR.HostVendorID, DataCenter: cfg.DataCenter},
 		bidValidationEnforcement: cfg.Validations,
 		requestSplitter:          requestSplitter,
+		macroReplacer:            macroReplacer,
 		floor:                    cfg.PriceFloors,
 	}
 }
diff --git a/hooks/hookexecution/execution.go b/hooks/hookexecution/execution.go
index 20fa76d8..18c92789 100644
--- a/hooks/hookexecution/execution.go
+++ b/hooks/hookexecution/execution.go
@@ -190,12 +190,10 @@ func handleHookResponse[P any](
 		ExecutionTime: ExecutionTime{ExecutionTimeMillis: hr.ExecutionTime},
 	}
 
-	switch true {
-	case hr.Err != nil:
+	if hr.Err != nil || hr.Result.Reject {
 		handleHookError(hr, &hookOutcome, metricEngine, labels)
-	case hr.Result.Reject:
 		rejectErr = handleHookReject(ctx, hr, &hookOutcome, metricEngine, labels)
-	default:
+	} else {
 		payload = handleHookMutations(payload, hr, &hookOutcome, metricEngine, labels)
 	}
 
diff --git a/macros/provider.go b/macros/provider.go
new file mode 100644
index 00000000..0b0fc0de
--- /dev/null
+++ b/macros/provider.go
@@ -0,0 +1,145 @@
+package macros
+
+import (
+	"net/url"
+	"strconv"
+	"time"
+
+	"github.com/prebid/prebid-server/exchange/entities"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+const (
+	MacroKeyBidID       = "PBS-BIDID"
+	MacroKeyAppBundle   = "PBS-APPBUNDLE"
+	MacroKeyDomain      = "PBS-DOMAIN"
+	MacroKeyPubDomain   = "PBS-PUBDOMAIN"
+	MacroKeyPageURL     = "PBS-PAGEURL"
+	MacroKeyAccountID   = "PBS-ACCOUNTID"
+	MacroKeyLmtTracking = "PBS-LIMITADTRACKING"
+	MacroKeyConsent     = "PBS-GDPRCONSENT"
+	MacroKeyBidder      = "PBS-BIDDER"
+	MacroKeyIntegration = "PBS-INTEGRATION"
+	MacroKeyVastCRTID   = "PBS-VASTCRTID"
+	MacroKeyTimestamp   = "PBS-TIMESTAMP"
+	MacroKeyAuctionID   = "PBS-AUCTIONID"
+	MacroKeyChannel     = "PBS-CHANNEL"
+	MacroKeyEventType   = "PBS-EVENTTYPE"
+	MacroKeyVastEvent   = "PBS-VASTEVENT"
+)
+
+const (
+	customMacroLength = 100
+	CustomMacroPrefix = "PBS-MACRO-"
+)
+
+type macroProvider struct {
+	// macros map stores macros key values
+	macros map[string]string
+}
+
+// NewBuilder returns the instance of macro buidler
+func NewProvider(reqWrapper *openrtb_ext.RequestWrapper) *macroProvider {
+	macroProvider := &macroProvider{macros: map[string]string{}}
+	macroProvider.populateRequestMacros(reqWrapper)
+	return macroProvider
+}
+
+func (b *macroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWrapper) {
+	b.macros[MacroKeyTimestamp] = strconv.Itoa(int(time.Now().Unix()))
+	reqExt, err := reqWrapper.GetRequestExt()
+	if err == nil && reqExt != nil {
+		if reqPrebidExt := reqExt.GetPrebid(); reqPrebidExt != nil {
+			for key, value := range reqPrebidExt.Macros {
+				customMacroKey := CustomMacroPrefix + key                     // Adding prefix PBS-MACRO to custom macro keys
+				b.macros[customMacroKey] = truncate(value, customMacroLength) // limit the custom macro value  to 100 chars only
+			}
+
+			if reqPrebidExt.Integration != "" {
+				b.macros[MacroKeyIntegration] = reqPrebidExt.Integration
+			}
+
+			if reqPrebidExt.Channel != nil {
+				b.macros[MacroKeyChannel] = reqPrebidExt.Channel.Name
+			}
+		}
+	}
+	b.macros[MacroKeyAuctionID] = reqWrapper.ID
+	if reqWrapper.App != nil {
+		if reqWrapper.App.Bundle != "" {
+			b.macros[MacroKeyAppBundle] = reqWrapper.App.Bundle
+		}
+
+		if reqWrapper.App.Domain != "" {
+			b.macros[MacroKeyDomain] = reqWrapper.App.Domain
+		}
+
+		if reqWrapper.App.Publisher != nil {
+			if reqWrapper.App.Publisher.Domain != "" {
+				b.macros[MacroKeyPubDomain] = reqWrapper.App.Publisher.Domain
+			}
+			if reqWrapper.App.Publisher.ID != "" {
+				b.macros[MacroKeyAccountID] = reqWrapper.App.Publisher.ID
+			}
+		}
+	}
+
+	if reqWrapper.Site != nil {
+		if reqWrapper.Site.Page != "" {
+			b.macros[MacroKeyPageURL] = reqWrapper.Site.Page
+		}
+
+		if reqWrapper.Site.Domain != "" {
+			b.macros[MacroKeyDomain] = reqWrapper.Site.Domain
+		}
+
+		if reqWrapper.Site.Publisher != nil {
+			if reqWrapper.Site.Publisher.Domain != "" {
+				b.macros[MacroKeyPubDomain] = reqWrapper.Site.Publisher.Domain
+			}
+
+			if reqWrapper.Site.Publisher.ID != "" {
+				b.macros[MacroKeyAccountID] = reqWrapper.Site.Publisher.ID
+			}
+		}
+	}
+
+	userExt, err := reqWrapper.GetUserExt()
+	if err == nil && userExt != nil && userExt.GetConsent() != nil {
+		b.macros[MacroKeyConsent] = *userExt.GetConsent()
+	}
+	if reqWrapper.Device != nil && reqWrapper.Device.Lmt != nil {
+		b.macros[MacroKeyLmtTracking] = strconv.Itoa(int(*reqWrapper.Device.Lmt))
+	}
+
+}
+
+func (b *macroProvider) GetMacro(key string) string {
+	return url.QueryEscape(b.macros[key])
+}
+
+func (b *macroProvider) PopulateBidMacros(bid *entities.PbsOrtbBid, seat string) {
+	if bid.Bid != nil {
+		if bid.GeneratedBidID != "" {
+			b.macros[MacroKeyBidID] = bid.GeneratedBidID
+		} else {
+			b.macros[MacroKeyBidID] = bid.Bid.ID
+		}
+	}
+	b.macros[MacroKeyBidder] = seat
+}
+
+func (b *macroProvider) PopulateEventMacros(vastCreativeID, eventType, vastEvent string) {
+	b.macros[MacroKeyVastCRTID] = vastCreativeID
+	b.macros[MacroKeyEventType] = eventType
+	b.macros[MacroKeyVastEvent] = vastEvent
+}
+
+func truncate(text string, width uint) string {
+	r := []rune(text)
+	if uint(len(r)) < (width) {
+		return text
+	}
+	trunc := r[:width]
+	return string(trunc)
+}
diff --git a/macros/replacer.go b/macros/replacer.go
new file mode 100644
index 00000000..e1a13d27
--- /dev/null
+++ b/macros/replacer.go
@@ -0,0 +1,7 @@
+package macros
+
+type Replacer interface {
+	// Replace the macros and returns replaced string
+	// if any error the error will be returned
+	Replace(url string, macroProvider *macroProvider) (string, error)
+}
diff --git a/macros/string_index_based_replacer.go b/macros/string_index_based_replacer.go
new file mode 100644
index 00000000..32748c1e
--- /dev/null
+++ b/macros/string_index_based_replacer.go
@@ -0,0 +1,104 @@
+package macros
+
+import (
+	"strings"
+	"sync"
+)
+
+const (
+	delimiter = "##"
+)
+
+type stringIndexBasedReplacer struct {
+	templates map[string]urlMetaTemplate
+	sync.RWMutex
+}
+
+type urlMetaTemplate struct {
+	startingIndices []int
+	endingIndices   []int
+}
+
+// NewStringIndexBasedReplacer will return instance of string index based macro replacer
+func NewStringIndexBasedReplacer() Replacer {
+	return &stringIndexBasedReplacer{
+		templates: make(map[string]urlMetaTemplate),
+	}
+}
+
+// constructTemplate func finds index bounds of all macros in an input string where macro format is ##data##.
+// constructTemplate func returns two arrays with start indexes and end indexes for all macros found in the input string.
+// Start index of the macro points to the index of the delimiter(##) start.
+// End index of the macro points to the end index of the delimiter.
+// For the valid input string number of start and end indexes should be equal, and they should not intersect.
+// This approach shows better performance results compare to standard GoLang string replacer.
+func constructTemplate(url string) urlMetaTemplate {
+	currentIndex := 0
+	tmplt := urlMetaTemplate{
+		startingIndices: []int{},
+		endingIndices:   []int{},
+	}
+	delimiterLen := len(delimiter)
+	for {
+		currentIndex = currentIndex + strings.Index(url[currentIndex:], delimiter)
+		if currentIndex == -1 {
+			break
+		}
+		startIndex := currentIndex + delimiterLen
+		endingIndex := strings.Index(url[startIndex:], delimiter)
+		if endingIndex == -1 {
+			break
+		}
+		endingIndex = endingIndex + startIndex - 1
+		tmplt.startingIndices = append(tmplt.startingIndices, startIndex)
+		tmplt.endingIndices = append(tmplt.endingIndices, endingIndex)
+		currentIndex = endingIndex + delimiterLen + 1
+		if currentIndex >= len(url)-1 {
+			break
+		}
+	}
+	return tmplt
+}
+
+// Replace function replaces macros in a given string with the data from macroProvider and returns modified input string.
+// If a given string was previously processed this function fetches its metadata from the cache.
+// If input string is not found in cache then template metadata will be created.
+// Iterates over start and end indexes of the template arrays and extracts macro name from the input string.
+// Gets the value of the extracted macro from the macroProvider. Replaces macro with corresponding value.
+func (s *stringIndexBasedReplacer) Replace(url string, macroProvider *macroProvider) (string, error) {
+	tmplt := s.getTemplate(url)
+
+	var result strings.Builder
+	currentIndex := 0
+	delimLen := len(delimiter)
+	for i, index := range tmplt.startingIndices {
+		macro := url[index : tmplt.endingIndices[i]+1]
+		// copy prev part
+		result.WriteString(url[currentIndex : index-delimLen])
+		value := macroProvider.GetMacro(macro)
+		if value != "" {
+			result.WriteString(value)
+		}
+		currentIndex = index + len(macro) + delimLen
+	}
+	result.WriteString(url[currentIndex:])
+	return result.String(), nil
+}
+
+func (s *stringIndexBasedReplacer) getTemplate(url string) urlMetaTemplate {
+	var (
+		template urlMetaTemplate
+		ok       bool
+	)
+	s.RLock()
+	template, ok = s.templates[url]
+	s.RUnlock()
+
+	if !ok {
+		s.Lock()
+		template = constructTemplate(url)
+		s.templates[url] = template
+		s.Unlock()
+	}
+	return template
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 52dd503f..9f74a710 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -173,7 +173,6 @@ const (
 	BidderGamoshi           BidderName = "gamoshi"
 	BidderGlobalsun         BidderName = "globalsun"
 	BidderGrid              BidderName = "grid"
-	BidderGroupm            BidderName = "groupm"
 	BidderGumGum            BidderName = "gumgum"
 	BidderHuaweiAds         BidderName = "huaweiads"
 	BidderIionads           BidderName = "iionads"
@@ -206,6 +205,7 @@ const (
 	BidderMgid              BidderName = "mgid"
 	BidderMobfoxpb          BidderName = "mobfoxpb"
 	BidderMobileFuse        BidderName = "mobilefuse"
+	BidderMotorik           BidderName = "motorik"
 	BidderNanoInteractive   BidderName = "nanointeractive"
 	BidderNextMillennium    BidderName = "nextmillennium"
 	BidderNinthDecimal      BidderName = "ninthdecimal"
@@ -365,7 +365,6 @@ func CoreBidderNames() []BidderName {
 		BidderGamoshi,
 		BidderGlobalsun,
 		BidderGrid,
-		BidderGroupm,
 		BidderGumGum,
 		BidderHuaweiAds,
 		BidderIionads,
@@ -398,6 +397,7 @@ func CoreBidderNames() []BidderName {
 		BidderMgid,
 		BidderMobfoxpb,
 		BidderMobileFuse,
+		BidderMotorik,
 		BidderNanoInteractive,
 		BidderNextMillennium,
 		BidderNinthDecimal,
diff --git a/openrtb_ext/imp_motorik.go b/openrtb_ext/imp_motorik.go
new file mode 100644
index 00000000..0b6fca78
--- /dev/null
+++ b/openrtb_ext/imp_motorik.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ExtMotorik struct {
+	AccountID   string `json:"accountId"`
+	PlacementID string `json:"placementId"`
+}
diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go
index a6c7d5dc..9d82e424 100644
--- a/openrtb_ext/request.go
+++ b/openrtb_ext/request.go
@@ -79,8 +79,11 @@ type ExtRequestPrebid struct {
 	// any other value or an empty string disables trace output at all.
 	Trace string `json:"trace,omitempty"`
 
-	BidAdjustments    *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"`
+	// Macros specifies list of custom macros along with the values. This is used while forming
+	// the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding
+	Macros            map[string]string               `json:"macros,omitempty"`
 	AdServerTargeting []AdServerTarget                `json:"adservertargeting,omitempty"`
+	BidAdjustments    *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"`
 }
 
 type AdServerTarget struct {
diff --git a/router/router.go b/router/router.go
index de120cfe..8fc99589 100644
--- a/router/router.go
+++ b/router/router.go
@@ -22,6 +22,7 @@ import (
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/hooks"
+	"github.com/prebid/prebid-server/macros"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConf "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/modules"
@@ -214,7 +215,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
 	}
 
 	planBuilder := hooks.NewExecutionPlanBuilder(cfg.Hooks, repo)
-	theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner)
+	macroReplacer := macros.NewStringIndexBasedReplacer()
+	theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, cfg.BidderInfos, gdprPermsBuilder, rateConvertor, categoriesFetcher, adsCertSigner, macroReplacer)
 	var uuidGenerator uuidutil.UUIDRandomGenerator
 	openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher, planBuilder)
 	if err != nil {
diff --git a/static/bidder-info/adsinteractive.yaml b/static/bidder-info/adsinteractive.yaml
index 0fb63903..ae60ec88 100644
--- a/static/bidder-info/adsinteractive.yaml
+++ b/static/bidder-info/adsinteractive.yaml
@@ -1,6 +1,7 @@
 endpoint: "http://bid.adsinteractive.com/prebid"
 maintainer:
   email: it@adsinteractive.com
+gvlVendorID: 1212
 modifyingVastXmlAllowed: false
 capabilities:
   app:
diff --git a/static/bidder-info/groupm.yaml b/static/bidder-info/groupm.yaml
deleted file mode 100644
index 189527d5..00000000
--- a/static/bidder-info/groupm.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-endpoint: "https://hbopenbid.pubmatic.com/translator?source=prebid-server"
-maintainer:
-  email: "header-bidding@pubmatic.com"
-gvlVendorID: 98
-capabilities:
-  app:
-    mediaTypes:
-      - banner
-      - video
-  site:
-    mediaTypes:
-      - banner
-      - video
-userSync:
-  iframe:
-    url: "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect={{.RedirectURL}}"
-    userMacro: ""
-    # groupm appends the user id to end of the redirect url and does not utilize a macro
-
-
diff --git a/static/bidder-info/motorik.yaml b/static/bidder-info/motorik.yaml
new file mode 100644
index 00000000..6818de9d
--- /dev/null
+++ b/static/bidder-info/motorik.yaml
@@ -0,0 +1,14 @@
+endpoint: "http://lb-east.motorik.io/?k={{.AccountID}}&name={{.SourceId}}"
+maintainer:
+  email: "support@motorik.io"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
\ No newline at end of file
diff --git a/static/bidder-params/groupm.json b/static/bidder-params/groupm.json
deleted file mode 100644
index cfebd2ad..00000000
--- a/static/bidder-params/groupm.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
-	"$schema": "http://json-schema.org/draft-04/schema#",
-	"title": "Groupm Adapter Params",
-	"description": "A schema which validates params accepted by the Groupm adapter",
-	"type": "object",
-	"properties": {
-		"publisherId": {
-			"type": "string",
-			"description": "An ID which identifies the publisher"
-		},
-		"adSlot": {
-			"type": "string",
-			"description": "An ID which identifies the ad slot"
-		},
-		"pmzoneid": {
-			"type": "string",
-			"description": "Comma separated zone id. Used im deal targeting & site section targeting. e.g drama,sport"
-		},
-		"dctr": {
-			"type": "string",
-			"description": "Deals Custom Targeting, pipe separated key-value pairs e.g key1=V1,V2,V3|key2=v1|key3=v3,v5"
-		},
-		"wrapper": {
-			"type": "object",
-			"description": "Specifies Groupm openwrap configuration for a publisher",
-			"properties": {
-				"profile": {
-					"type": "integer",
-					"description": "An ID which identifies the openwrap profile of publisher"
-				},
-				"version": {
-					"type": "integer",
-					"description": "An ID which identifies version of the openwrap profile"
-				}
-			},
-			"required": ["profile"]
-		},
-		"keywords": {
-			"type": "array",
-			"minItems": 1,
-			"items": {
-			  "type": "object",
-			  "description": "A key with one or more values associated with it. These are used in buy-side segment targeting.",
-			  "properties": {
-				"key": {
-				  "type": "string"
-				},
-				"value": {
-				  "type": "array",
-				  "minItems": 1,
-				  "items": {
-					"type": "string"
-				  }
-				}
-			  },
-			  "required": ["key", "value"]
-			}
-		  }
-	},
-	"required": ["publisherId"]
-}
diff --git a/static/bidder-params/motorik.json b/static/bidder-params/motorik.json
new file mode 100644
index 00000000..261e9c1e
--- /dev/null
+++ b/static/bidder-params/motorik.json
@@ -0,0 +1,19 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Motorik Adapter Params",
+    "description": "A schema which validates params accepted by the Motorik adapter",
+    "type": "object",
+    "properties": {
+      "accountId": {
+        "type": "string",
+        "description": "Account id",
+        "minLength": 1
+      },
+      "placementId": {
+        "type": "string",
+        "description": "Placement id",
+        "minLength": 1
+      }
+    },
+    "required": ["accountId", "placementId"]
+  }
\ No newline at end of file
diff --git a/usersync/cookie.go b/usersync/cookie.go
index 51474afd..4f80fa5d 100644
--- a/usersync/cookie.go
+++ b/usersync/cookie.go
@@ -23,9 +23,8 @@ const uidTTL = 14 * 24 * time.Hour
 // To get an instance of this from a request, use ParseCookieFromRequest.
 // To write an instance onto a response, use SetCookieOnResponse.
 type Cookie struct {
-	uids     map[string]uidWithExpiry
-	optOut   bool
-	birthday *time.Time
+	uids   map[string]uidWithExpiry
+	optOut bool
 }
 
 // uidWithExpiry bundles the UID with an Expiration date.
@@ -83,8 +82,7 @@ func ParseCookie(httpCookie *http.Cookie) *Cookie {
 // NewCookie returns a new empty cookie.
 func NewCookie() *Cookie {
 	return &Cookie{
-		uids:     make(map[string]uidWithExpiry),
-		birthday: timestamp(),
+		uids: make(map[string]uidWithExpiry),
 	}
 }
 
@@ -226,16 +224,14 @@ func (cookie *Cookie) TrySync(key string, uid string) error {
 // This exists so that Cookie (which is public) can have private fields, and the rest of
 // the code doesn't have to worry about the cookie data storage format.
 type cookieJson struct {
-	UIDs     map[string]uidWithExpiry `json:"tempUIDs,omitempty"`
-	OptOut   bool                     `json:"optout,omitempty"`
-	Birthday *time.Time               `json:"bday,omitempty"`
+	UIDs   map[string]uidWithExpiry `json:"tempUIDs,omitempty"`
+	OptOut bool                     `json:"optout,omitempty"`
 }
 
 func (cookie *Cookie) MarshalJSON() ([]byte, error) {
 	return json.Marshal(cookieJson{
-		UIDs:     cookie.uids,
-		OptOut:   cookie.optOut,
-		Birthday: cookie.birthday,
+		UIDs:   cookie.uids,
+		OptOut: cookie.optOut,
 	})
 }
 
@@ -246,7 +242,6 @@ func (cookie *Cookie) UnmarshalJSON(b []byte) error {
 	}
 
 	cookie.optOut = cookieContract.OptOut
-	cookie.birthday = cookieContract.Birthday
 
 	if cookie.optOut {
 		cookie.uids = nil
@@ -265,8 +260,3 @@ func (cookie *Cookie) UnmarshalJSON(b []byte) error {
 
 	return nil
 }
-
-func timestamp() *time.Time {
-	birthday := time.Now()
-	return &birthday
-}


Test patch:
diff --git a/adapters/motorik/motorik_test.go b/adapters/motorik/motorik_test.go
new file mode 100644
index 00000000..1a14feda
--- /dev/null
+++ b/adapters/motorik/motorik_test.go
@@ -0,0 +1,28 @@
+package motorik
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderMotorik, config.Adapter{
+		Endpoint: "http://us.example.com/?k={{.AccountID}}&name={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "motoriktest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderMotorik, config.Adapter{
+		Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/motorik/motoriktest/exemplary/banner-app.json b/adapters/motorik/motoriktest/exemplary/banner-app.json
new file mode 100644
index 00000000..18d8efc7
--- /dev/null
+++ b/adapters/motorik/motoriktest/exemplary/banner-app.json
@@ -0,0 +1,150 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "1",
+        "tagid": "ogTAGID",
+        "banner": {
+          "w": 320,
+          "h": 50
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "1",
+              "banner": {
+                "w": 320,
+                "h": 50
+              },
+              "tagid": "ogTAGID"
+            }
+          ],
+          "app": {
+            "id": "123456789",
+            "name": "Awesome App",
+            "bundle": "com.app.awesome",
+            "domain": "awesomeapp.com",
+            "cat": [
+              "IAB22-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "1",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 320,
+                  "h": 50
+                }
+              ],
+              "type": "banner",
+              "seat": "motorik"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "1",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "adomain": [
+              "awesome.com"
+            ],
+            "crid": "20",
+            "w": 320,
+            "h": 50
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/exemplary/banner-web.json b/adapters/motorik/motoriktest/exemplary/banner-web.json
new file mode 100644
index 00000000..024b553a
--- /dev/null
+++ b/adapters/motorik/motoriktest/exemplary/banner-web.json
@@ -0,0 +1,193 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id1",
+        "tagid": "ogTAGID",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      },     
+       {
+        "id": "some-impression-id2",
+        "tagid": "ogTAGID",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "some-impression-id1",
+              "tagid": "ogTAGID",
+              "banner": {
+                "w":320,
+                "h":50
+              }
+            },
+            {
+              "id": "some-impression-id2",
+              "tagid": "ogTAGID",
+              "banner": {
+                "w":320,
+                "h":50
+              },
+              "ext": {
+                "bidder": {
+                  "accountId": "accountId",
+                  "placementId": "placementId"
+              }
+            }
+        }
+          ],
+          "site": {
+            "page": "test.com",
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "some-impression-id1",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 320,
+                  "h": 50
+                },
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e162",
+                  "impid": "some-impression-id2",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 320,
+                  "h": 50
+                }
+              ],
+              "type": "banner",
+              "seat": "motorik"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids":[
+        {
+          "bid":  {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "some-impression-id1",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ],
+            "w": 320,
+            "h": 50
+          },
+          "type": "banner"
+        },
+        {
+          "bid":  {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e162",
+            "impid": "some-impression-id2",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ],
+            "w": 320,
+            "h": 50
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/exemplary/native-app.json b/adapters/motorik/motoriktest/exemplary/native-app.json
new file mode 100644
index 00000000..2f917f02
--- /dev/null
+++ b/adapters/motorik/motoriktest/exemplary/native-app.json
@@ -0,0 +1,147 @@
+{
+    "mockBidRequest": {
+      "id": "some-request-id",
+      "device": {
+        "ua": "test-user-agent",
+        "ip": "123.123.123.123",
+        "language": "en",
+        "dnt": 0
+      },
+      "tmax": 1000,
+      "user": {
+        "buyeruid": "awesome-user"
+      },
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB22-1"
+        ],
+        "bundle": "com.app.awesome",
+        "name": "Awesome App",
+        "domain": "awesomeapp.com",
+        "id": "123456789"
+      },
+      "imp": [
+        {
+          "id": "some-impression-id",
+          "tagid": "ogTAGID",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+          "ext": {
+            "bidder": {
+              "accountId": "accountId",
+              "placementId": "placementId"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "headers": {
+            "Content-Type": [
+              "application/json;charset=utf-8"
+            ],
+            "Accept": [
+              "application/json"
+            ],
+            "X-Openrtb-Version": [
+              "2.5"
+            ],
+            "User-Agent": [
+              "test-user-agent"
+            ],
+            "X-Forwarded-For": [
+              "123.123.123.123"
+            ]
+          },
+          "uri": "http://us.example.com/?k=accountId&name=placementId",
+          "body": {
+            "id": "some-request-id",
+            "device": {
+              "ua": "test-user-agent",
+              "ip": "123.123.123.123",
+              "language": "en",
+              "dnt": 0
+            },
+            "imp": [
+              {
+                "id": "some-impression-id",
+                "native": {
+                  "ver":"1.1",
+                  "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                },
+                "tagid": "ogTAGID"
+              }
+            ],
+            "app": {
+              "id": "123456789",
+              "name": "Awesome App",
+              "bundle": "com.app.awesome",
+              "domain": "awesomeapp.com",
+              "cat": [
+                "IAB22-1"
+              ],
+              "publisher": {
+                "id": "123456789"
+              }
+            },
+            "user": {
+              "buyeruid": "awesome-user"
+            },
+            "tmax": 1000
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "awesome-resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                    "impid": "some-impression-id",
+                    "price": 3.5,
+                    "adm": "awesome-markup",
+                    "adomain": [
+                      "awesome.com"
+                    ],
+                    "crid": "20"
+                  }
+                ],
+                "type": "native",
+                "seat": "motorik"
+              }
+            ],
+            "cur": "USD"
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids":[
+          {
+            "bid":  {
+              "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+              "impid": "some-impression-id",
+              "price": 3.5,
+              "adm": "awesome-markup",
+              "crid": "20",
+              "adomain": [
+                "awesome.com"
+              ]
+            },
+            "type": "native"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/exemplary/native-web.json b/adapters/motorik/motoriktest/exemplary/native-web.json
new file mode 100644
index 00000000..7ffc0bb2
--- /dev/null
+++ b/adapters/motorik/motoriktest/exemplary/native-web.json
@@ -0,0 +1,133 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "native": {
+          "ver":"1.1",
+          "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "2607:fb90:f27:4512:d800:cb23:a603:e245"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "tagid": "ogTAGID",
+              "native": {
+                "ver":"1.1",
+                "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+              }
+            }
+          ],
+          "site": {
+            "page": "test.com",
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "some-impression-id",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20"
+                }
+              ],
+              "seat": "motorik"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids":[
+        {
+          "bid":  {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "some-impression-id",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ]
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/exemplary/video-app.json b/adapters/motorik/motoriktest/exemplary/video-app.json
new file mode 100644
index 00000000..caee18cd
--- /dev/null
+++ b/adapters/motorik/motoriktest/exemplary/video-app.json
@@ -0,0 +1,159 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 120,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "ogTAGID"
+            }
+          ],
+          "app": {
+            "id": "123456789",
+            "name": "Awesome App",
+            "bundle": "com.app.awesome",
+            "domain": "awesomeapp.com",
+            "cat": [
+              "IAB22-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "some-impression-id",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+              "seat": "motorik"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids":[
+        {
+          "bid":  {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "some-impression-id",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ],
+            "w": 1280,
+            "h": 720
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/exemplary/video-web.json b/adapters/motorik/motoriktest/exemplary/video-web.json
new file mode 100644
index 00000000..0c3afebc
--- /dev/null
+++ b/adapters/motorik/motoriktest/exemplary/video-web.json
@@ -0,0 +1,157 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "tagid": "ogTAGID",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 120,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              }
+            }
+          ],
+          "site": {
+            "page": "test.com",
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "some-impression-id",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 1280,
+                  "h": 720,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                }
+              ],
+              "seat": "motorik"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "some-impression-id",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "adomain": [
+              "awesome.com"
+            ],
+            "crid": "20",
+            "w": 1280,
+            "h": 720,
+            "ext": {
+              "prebid": {
+                "type": "video"
+              }
+            }
+          },
+          "type":"video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/supplemental/bad_media_type.json b/adapters/motorik/motoriktest/supplemental/bad_media_type.json
new file mode 100644
index 00000000..103b9cf8
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/bad_media_type.json
@@ -0,0 +1,135 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "1",
+        "tagid": "ogTAGID",
+        "banner": {
+          "w": 320,
+          "h": 50
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "1",
+              "banner": {
+                "w": 320,
+                "h": 50
+              },
+              "tagid": "ogTAGID"
+            }
+          ],
+          "app": {
+            "id": "123456789",
+            "name": "Awesome App",
+            "bundle": "com.app.awesome",
+            "domain": "awesomeapp.com",
+            "cat": [
+              "IAB22-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 320,
+                  "h": 50
+                }
+              ],
+              "type": "banner",
+              "seat": "motorik"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Failed to find impression \"test-imp-id\"",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/supplemental/empty-seatbid-array.json b/adapters/motorik/motoriktest/supplemental/empty-seatbid-array.json
new file mode 100644
index 00000000..9447d661
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/empty-seatbid-array.json
@@ -0,0 +1,131 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ],
+          "X-Openrtb-Version": [
+            "2.5"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ]
+        },
+        "uri": "http://us.example.com/?k=accountId&name=placementId",
+        "body": {
+          "id": "some-request-id",
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "language": "en",
+            "dnt": 0
+          },
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 120,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "ogTAGID"
+            }
+          ],
+          "app": {
+            "id": "123456789",
+            "name": "Awesome App",
+            "bundle": "com.app.awesome",
+            "domain": "awesomeapp.com",
+            "cat": [
+              "IAB22-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    },
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Empty SeatBid array",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/supplemental/invalid-bidder-ext-object.json b/adapters/motorik/motoriktest/supplemental/invalid-bidder-ext-object.json
new file mode 100644
index 00000000..fb89a589
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/invalid-bidder-ext-object.json
@@ -0,0 +1,33 @@
+{
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Error parsing bidderExt - json: cannot unmarshal string into Go value of type openrtb_ext.ExtMotorik",
+      "comparison": "literal"
+    }
+  ],
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "my-adcode",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": "wrongBidderExt"
+        }
+      }
+    ],
+    "site": {
+      "page": "test.com"
+    }
+  },
+  "httpCalls": []
+}
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/supplemental/invalid-motorik-ext-object.json b/adapters/motorik/motoriktest/supplemental/invalid-motorik-ext-object.json
new file mode 100644
index 00000000..1b2cce8d
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/invalid-motorik-ext-object.json
@@ -0,0 +1,31 @@
+{
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Error parsing motorikExt - json: cannot unmarshal string into Go value of type adapters.ExtImpBidder",
+      "comparison": "literal"
+    }
+  ],
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "my-adcode",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": "wrongMotorikExt"
+      }
+    ],
+    "site": {
+      "page": "test.com"
+    }
+  },
+  "httpCalls": []
+}
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/supplemental/invalid-response.json b/adapters/motorik/motoriktest/supplemental/invalid-response.json
new file mode 100644
index 00000000..c497f9df
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/invalid-response.json
@@ -0,0 +1,112 @@
+
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ],
+        "X-Openrtb-Version": [
+          "2.5"
+        ],
+        "User-Agent": [
+          "test-user-agent"
+        ],
+        "X-Forwarded-For": [
+          "123.123.123.123"
+        ]
+      },
+      "uri": "http://us.example.com/?k=accountId&name=placementId",
+      "body": {
+        "id": "some-request-id",
+        "device": {
+          "ua": "test-user-agent",
+          "ip": "123.123.123.123",
+          "language": "en",
+          "dnt": 0
+        },
+        "imp": [
+          {
+            "id": "some-impression-id",
+            "banner": {
+                "w":320,
+                "h":50
+            },
+            "tagid": "ogTAGID"
+          }
+        ],
+        "app": {
+          "id": "123456789",
+          "name": "Awesome App",
+          "bundle": "com.app.awesome",
+          "domain": "awesomeapp.com",
+          "cat": [
+            "IAB22-1"
+          ],
+          "publisher": {
+            "id": "123456789"
+          }
+        },
+        "user": {
+          "buyeruid": "awesome-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Server Response",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/supplemental/status-code-bad-request.json b/adapters/motorik/motoriktest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000..293c74b8
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/status-code-bad-request.json
@@ -0,0 +1,93 @@
+
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB22-1"
+      ],
+      "bundle": "com.app.awesome",
+      "name": "Awesome App",
+      "domain": "awesomeapp.com",
+      "id": "123456789"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://us.example.com/?k=accountId&name=placementId",
+      "body": {
+        "id": "some-request-id",
+        "imp": [
+          {
+            "id": "some-impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 120,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "ogTAGID"
+          }
+        ],
+        "app":  {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB22-1"
+          ],
+          "bundle": "com.app.awesome",
+          "name": "Awesome App",
+          "domain": "awesomeapp.com",
+          "id": "123456789"
+        },
+        "user": {
+          "buyeruid": "awesome-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 400
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/supplemental/status-code-no-content.json b/adapters/motorik/motoriktest/supplemental/status-code-no-content.json
new file mode 100644
index 00000000..095b869e
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/status-code-no-content.json
@@ -0,0 +1,69 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [{
+      "id": "some-impression-id",
+      "tagid": "ogTAGID",
+      "video": {
+        "mimes": ["video/mp4"],
+        "w": 640,
+        "h": 480,
+        "minduration": 120,
+        "maxduration": 150
+      },
+      "ext": {
+        "bidder": {
+          "accountId": "accountId",
+          "placementId": "placementId"
+        }
+      }
+    }]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://us.example.com/?k=accountId&name=placementId",
+      "body": {
+        "id": "some-request-id",
+        "imp": [{
+          "id": "some-impression-id",
+          "tagid": "ogTAGID",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ],
+            "minduration": 120,
+            "maxduration": 150,
+            "w": 640,
+            "h": 480
+          }
+        }],
+        "site": {
+          "page": "test.com",
+          "publisher": {
+            "id": "123456789"
+          }
+        },
+        "user": {
+          "buyeruid": "awesome-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 204
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/motorik/motoriktest/supplemental/status-code-other-error.json b/adapters/motorik/motoriktest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000..95e607be
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/status-code-other-error.json
@@ -0,0 +1,79 @@
+
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": ["video/mp4"],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://us.example.com/?k=accountId&name=placementId",
+      "body": {
+        "id": "some-request-id",
+        "imp": [
+          {
+            "id": "some-impression-id",
+            "tagid": "ogTAGID",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 120,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            }
+          }
+        ],
+        "site": {
+          "page": "test.com",
+          "publisher": {
+            "id": "123456789"
+          }
+        },
+        "user": {
+          "buyeruid": "awesome-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 306
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 306. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/motorik/motoriktest/supplemental/status-code-service-unavailable.json b/adapters/motorik/motoriktest/supplemental/status-code-service-unavailable.json
new file mode 100644
index 00000000..aa03e6f1
--- /dev/null
+++ b/adapters/motorik/motoriktest/supplemental/status-code-service-unavailable.json
@@ -0,0 +1,79 @@
+
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "user": {
+      "buyeruid": "awesome-user"
+    },
+    "site": {
+      "page": "test.com",
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "tagid": "ogTAGID",
+        "video": {
+          "mimes": ["video/mp4"],
+          "w": 640,
+          "h": 480,
+          "minduration": 120,
+          "maxduration": 150
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "placementId": "placementId"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "http://us.example.com/?k=accountId&name=placementId",
+      "body": {
+        "id": "some-request-id",
+        "imp": [
+          {
+            "id": "some-impression-id",
+            "tagid": "ogTAGID",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 120,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            }
+          }
+        ],
+        "site": {
+          "page": "test.com",
+          "publisher": {
+            "id": "123456789"
+          }
+        },
+        "user": {
+          "buyeruid": "awesome-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 503
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Something went wrong Status Code: [ 503 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/motorik/params_test.go b/adapters/motorik/params_test.go
new file mode 100644
index 00000000..9ee05b58
--- /dev/null
+++ b/adapters/motorik/params_test.go
@@ -0,0 +1,50 @@
+package motorik
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+var validParams = []string{
+	`{  "accountId": "hash",  "placementId": "hash"}`,
+}
+
+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.BidderMotorik, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Motorik params: %s", validParam)
+		}
+	}
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
+	`{  "accountid": "" }`,
+}
+
+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.BidderMotorik, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 8eb80672..961ff56a 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -2108,30 +2108,30 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) {
 		{
 			description: "Assert correct AmpResponse when request rejected at entrypoint stage",
 			file:        "sample-requests/hooks/amp_entrypoint_reject.json",
-			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, nil})},
 		},
 		{
 			// raw_auction stage not executed for AMP endpoint, so we expect full response
 			description: "Assert correct AmpResponse when request rejected at raw_auction stage",
 			file:        "sample-requests/amp/valid-supplementary/aliased-buyeruids.json",
-			planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr, nil})},
 		},
 		{
 			description: "Assert correct AmpResponse when request rejected at processed_auction stage",
 			file:        "sample-requests/hooks/amp_processed_auction_request_reject.json",
-			planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr, nil})},
 		},
 		{
 			// bidder_request stage rejects only bidder, so we expect bidder rejection warning added
 			description: "Assert correct AmpResponse when request rejected at bidder-request stage",
 			file:        "sample-requests/hooks/amp_bidder_reject.json",
-			planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr, nil})},
 		},
 		{
 			// raw_bidder_response stage rejects only bidder, so we expect bidder rejection warning added
 			description: "Assert correct AmpResponse when request rejected at raw_bidder_response stage",
 			file:        "sample-requests/hooks/amp_bidder_response_reject.json",
-			planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr, nil})},
 		},
 		{
 			// no debug information should be added for raw_auction stage because it's not executed for amp endpoint
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index 2866a31e..9ad3c768 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/experiment/adscert"
 	"github.com/prebid/prebid-server/hooks"
+	"github.com/prebid/prebid-server/macros"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
@@ -93,6 +94,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 		currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		empty_fetcher.EmptyFetcher{},
 		&adscert.NilSigner{},
+		macros.NewStringIndexBasedReplacer(),
 	)
 
 	endpoint, _ := NewEndpoint(
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 544a46d0..8753a27b 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -5084,28 +5084,33 @@ func TestValidResponseAfterExecutingStages(t *testing.T) {
 		{
 			description: "Assert correct BidResponse when request rejected at entrypoint stage",
 			file:        "sample-requests/hooks/auction_entrypoint_reject.json",
-			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, nil})},
 		},
 		{
 			description: "Assert correct BidResponse when request rejected at raw-auction stage",
 			file:        "sample-requests/hooks/auction_raw_auction_request_reject.json",
-			planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr, nil})},
 		},
 		{
 			description: "Assert correct BidResponse when request rejected at processed-auction stage",
 			file:        "sample-requests/hooks/auction_processed_auction_request_reject.json",
-			planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr, nil})},
 		},
 		{
 			// bidder-request stage doesn't reject whole request, so we do not expect NBR code in response
 			description: "Assert correct BidResponse when request rejected at bidder-request stage",
 			file:        "sample-requests/hooks/auction_bidder_reject.json",
-			planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr, nil})},
 		},
 		{
 			description: "Assert correct BidResponse when request rejected at raw-bidder-response stage",
 			file:        "sample-requests/hooks/auction_bidder_response_reject.json",
-			planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr})},
+			planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr, nil})},
+		},
+		{
+			description: "Assert correct BidResponse when request rejected with error from hook",
+			file:        "sample-requests/hooks/auction_reject_with_error.json",
+			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, errors.New("dummy")})},
 		},
 		{
 			description: "Assert correct BidResponse with debug information from modules added to ext.prebid.modules",
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index 814f2611..6dfd9ab6 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -30,6 +30,7 @@ import (
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/hooks/hookstage"
+	"github.com/prebid/prebid-server/macros"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -1211,6 +1212,7 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid
 		mockCurrencyConverter,
 		mockFetcher,
 		&adscert.NilSigner{},
+		macros.NewStringIndexBasedReplacer(),
 	)
 
 	testExchange = &exchangeTestWrapper{
@@ -1466,6 +1468,7 @@ func makePlan[H any](hook H) hooks.Plan[H] {
 
 type mockRejectionHook struct {
 	nbr int
+	err error
 }
 
 func (m mockRejectionHook) HandleEntrypointHook(
@@ -1473,7 +1476,7 @@ func (m mockRejectionHook) HandleEntrypointHook(
 	_ hookstage.ModuleInvocationContext,
 	_ hookstage.EntrypointPayload,
 ) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
-	return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true, NbrCode: m.nbr}, nil
+	return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true, NbrCode: m.nbr}, m.err
 }
 
 func (m mockRejectionHook) HandleRawAuctionHook(
@@ -1481,7 +1484,7 @@ func (m mockRejectionHook) HandleRawAuctionHook(
 	_ hookstage.ModuleInvocationContext,
 	_ hookstage.RawAuctionRequestPayload,
 ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
-	return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, nil
+	return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err
 }
 
 func (m mockRejectionHook) HandleProcessedAuctionHook(
@@ -1489,7 +1492,7 @@ func (m mockRejectionHook) HandleProcessedAuctionHook(
 	_ hookstage.ModuleInvocationContext,
 	_ hookstage.ProcessedAuctionRequestPayload,
 ) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) {
-	return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, nil
+	return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err
 }
 
 func (m mockRejectionHook) HandleBidderRequestHook(
@@ -1503,7 +1506,7 @@ func (m mockRejectionHook) HandleBidderRequestHook(
 		result.NbrCode = m.nbr
 	}
 
-	return result, nil
+	return result, m.err
 }
 
 func (m mockRejectionHook) HandleRawBidderResponseHook(
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index 4a6f468e..b08a85de 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -213,6 +213,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
+				"groupm":         `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -225,6 +226,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"yssp":           `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahoossp" in your configuration.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
+				"groupm":         `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -238,6 +240,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"appnexus":       `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
+				"groupm":         `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 		{
@@ -251,6 +254,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
 				"appnexus":       `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
 				"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
 				"oftmedia":       `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
+				"groupm":         `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
 			},
 		},
 	}
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 4e94a18f..87d04185 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -2725,7 +2725,7 @@ func TestExtraBidWithBidAdjustments(t *testing.T) {
 	}
 	bidAdjustments := map[string]float64{
 		string(openrtb_ext.BidderPubmatic): 2,
-		string(openrtb_ext.BidderGroupm):   3,
+		"groupm":                           3,
 	}
 
 	bidReqOptions := bidRequestOptions{
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 367d3f5b..7789a841 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -30,6 +30,7 @@ import (
 	"github.com/prebid/prebid-server/hooks"
 	"github.com/prebid/prebid-server/hooks/hookexecution"
 	"github.com/prebid/prebid-server/hooks/hookstage"
+	"github.com/prebid/prebid-server/macros"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConf "github.com/prebid/prebid-server/metrics/config"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
@@ -79,7 +80,7 @@ func TestNewExchange(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 	for _, bidderName := range knownAdapters {
 		if _, ok := e.adapterMap[bidderName]; !ok {
 			if biddersInfo[string(bidderName)].IsEnabled() {
@@ -129,7 +130,7 @@ func TestCharacterEscape(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	//liveAdapters []openrtb_ext.BidderName,
@@ -1424,7 +1425,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
 
@@ -1784,7 +1785,7 @@ func TestBidResponseCurrency(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -1931,7 +1932,7 @@ func TestBidResponseImpExtInfo(t *testing.T) {
 		t.Fatalf("Error intializing adapters: %v", adaptersErr)
 	}
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -2024,7 +2025,7 @@ func TestRaceIntegration(t *testing.T) {
 		},
 	}.Builder
 
-	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 	_, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
@@ -2122,7 +2123,7 @@ func TestPanicRecovery(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 
 	chBids := make(chan *bidResponseWrapper, 1)
 	panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) {
@@ -2192,7 +2193,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 			allowAllBidders: true,
 		},
 	}.Builder
-	e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}).(*exchange)
+	e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
 
 	e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{}
 	e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{}
@@ -4657,7 +4658,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer).(*exchange)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer()).(*exchange)
 
 	// Define mock incoming bid requeset
 	mockBidRequest := &openrtb2.BidRequest{
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 56ba0e1d..51758a76 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -3863,7 +3863,7 @@ func Test_isBidderInExtAlternateBidderCodes(t *testing.T) {
 			name: "adapter defined in alternatebiddercodes but currentMultiBidBidder not in AllowedBidders list",
 			args: args{
 				adapter:               string(openrtb_ext.BidderPubmatic),
-				currentMultiBidBidder: string(openrtb_ext.BidderGroupm),
+				currentMultiBidBidder: "groupm",
 				adapterABC: &openrtb_ext.ExtAlternateBidderCodes{
 					Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 						string(openrtb_ext.BidderPubmatic): {
@@ -3878,11 +3878,11 @@ func Test_isBidderInExtAlternateBidderCodes(t *testing.T) {
 			name: "adapter defined in alternatebiddercodes with currentMultiBidBidder mentioned in AllowedBidders list",
 			args: args{
 				adapter:               string(openrtb_ext.BidderPubmatic),
-				currentMultiBidBidder: string(openrtb_ext.BidderGroupm),
+				currentMultiBidBidder: "groupm",
 				adapterABC: &openrtb_ext.ExtAlternateBidderCodes{
 					Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 						string(openrtb_ext.BidderPubmatic): {
-							AllowedBidderCodes: []string{string(openrtb_ext.BidderGroupm)},
+							AllowedBidderCodes: []string{"groupm"},
 						},
 					},
 				},
@@ -3893,7 +3893,7 @@ func Test_isBidderInExtAlternateBidderCodes(t *testing.T) {
 			name: "adapter defined in alternatebiddercodes with AllowedBidders list as *",
 			args: args{
 				adapter:               string(openrtb_ext.BidderPubmatic),
-				currentMultiBidBidder: string(openrtb_ext.BidderGroupm),
+				currentMultiBidBidder: "groupm",
 				adapterABC: &openrtb_ext.ExtAlternateBidderCodes{
 					Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 						string(openrtb_ext.BidderPubmatic): {
@@ -4004,7 +4004,7 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 				adapter: string(openrtb_ext.BidderPubmatic),
 				reqMultiBid: []*openrtb_ext.ExtMultiBid{
 					{
-						Bidder:  string(openrtb_ext.BidderGroupm),
+						Bidder:  "groupm",
 						MaxBids: ptrutil.ToPtr(3),
 					},
 					{
@@ -4019,14 +4019,14 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 				adapterABC: &openrtb_ext.ExtAlternateBidderCodes{
 					Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 						string(openrtb_ext.BidderPubmatic): {
-							AllowedBidderCodes: []string{string(openrtb_ext.BidderGroupm)},
+							AllowedBidderCodes: []string{"groupm"},
 						},
 					},
 				},
 			},
 			want: []*openrtb_ext.ExtMultiBid{
 				{
-					Bidder:  string(openrtb_ext.BidderGroupm),
+					Bidder:  "groupm",
 					MaxBids: ptrutil.ToPtr(3),
 				},
 				{
@@ -4041,7 +4041,7 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 				adapter: string(openrtb_ext.BidderAppnexus),
 				reqMultiBid: []*openrtb_ext.ExtMultiBid{
 					{
-						Bidder:  string(openrtb_ext.BidderGroupm),
+						Bidder:  "groupm",
 						MaxBids: ptrutil.ToPtr(3),
 					},
 					{
@@ -4056,14 +4056,14 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 				adapterABC: &openrtb_ext.ExtAlternateBidderCodes{
 					Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
 						string(openrtb_ext.BidderAppnexus): {
-							AllowedBidderCodes: []string{string(openrtb_ext.BidderGroupm)},
+							AllowedBidderCodes: []string{"groupm"},
 						},
 					},
 				},
 			},
 			want: []*openrtb_ext.ExtMultiBid{
 				{
-					Bidder:  string(openrtb_ext.BidderGroupm),
+					Bidder:  "groupm",
 					MaxBids: ptrutil.ToPtr(3),
 				},
 				{
@@ -4078,7 +4078,7 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 				adapter: string(openrtb_ext.BidderPubmatic),
 				reqMultiBid: []*openrtb_ext.ExtMultiBid{
 					{
-						Bidder:  string(openrtb_ext.BidderGroupm),
+						Bidder:  "groupm",
 						MaxBids: ptrutil.ToPtr(3),
 					},
 					{
@@ -4100,7 +4100,7 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 			},
 			want: []*openrtb_ext.ExtMultiBid{
 				{
-					Bidder:  string(openrtb_ext.BidderGroupm),
+					Bidder:  "groupm",
 					MaxBids: ptrutil.ToPtr(3),
 				},
 				{
@@ -4123,7 +4123,7 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 				adapter: string(openrtb_ext.BidderAppnexus),
 				reqMultiBid: []*openrtb_ext.ExtMultiBid{
 					{
-						Bidder:  string(openrtb_ext.BidderGroupm),
+						Bidder:  "groupm",
 						MaxBids: ptrutil.ToPtr(3),
 					},
 					{
@@ -4145,7 +4145,7 @@ func Test_buildRequestExtMultiBid(t *testing.T) {
 			},
 			want: []*openrtb_ext.ExtMultiBid{
 				{
-					Bidder:  string(openrtb_ext.BidderGroupm),
+					Bidder:  "groupm",
 					MaxBids: ptrutil.ToPtr(3),
 				},
 				{
diff --git a/macros/provider_test.go b/macros/provider_test.go
new file mode 100644
index 00000000..b6465a7f
--- /dev/null
+++ b/macros/provider_test.go
@@ -0,0 +1,464 @@
+package macros
+
+import (
+	"testing"
+
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/exchange/entities"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetMacro(t *testing.T) {
+	type args struct {
+		key            string
+		reqWrapper     *openrtb_ext.RequestWrapper
+		seat           string
+		vastEvent      string
+		eventType      string
+		vastCreativeID string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: " Macro present, get PBS-APPBUNDLE key",
+			args: args{
+				key: MacroKeyAppBundle,
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						ID: "123",
+						App: &openrtb2.App{
+							Bundle: "test",
+						},
+					},
+				},
+			},
+			want: "test",
+		},
+		{
+			name: " Macro does present, get PBS-APPBUNDLE key",
+			args: args{
+				key: MacroKeyAppBundle,
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						ID: "123",
+					},
+				},
+			},
+			want: "",
+		},
+		{
+			name: "Invalid Macro key",
+			args: args{
+				key: "PBS-NOEXISTENTKEY",
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Ext: []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR3":"a"}}}`),
+					},
+				},
+			},
+			want: "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			macroProvider := NewProvider(tt.args.reqWrapper)
+			macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, tt.args.seat)
+			macroProvider.PopulateEventMacros(tt.args.vastCreativeID, tt.args.eventType, tt.args.vastEvent)
+			got := macroProvider.GetMacro(tt.args.key)
+			assert.Equal(t, tt.want, got, tt.name)
+		})
+	}
+}
+
+func TestPopulateRequestMacros(t *testing.T) {
+	type args struct {
+		reqWrapper *openrtb_ext.RequestWrapper
+	}
+	tests := []struct {
+		name string
+		args args
+		want map[string]string
+	}{
+		{
+			name: "No request level macros present",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{},
+				},
+			},
+			want: map[string]string{MacroKeyBidID: "", MacroKeyAppBundle: "", MacroKeyDomain: "", MacroKeyPubDomain: "", MacroKeyPageURL: "", MacroKeyAccountID: "", MacroKeyLmtTracking: "", MacroKeyConsent: "", MacroKeyBidder: "", MacroKeyIntegration: "", MacroKeyVastCRTID: "", MacroKeyAuctionID: "", MacroKeyChannel: "", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+		{
+			name: " AUCTIONID, AppBundle, PageURL present key",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						ID: "123",
+						App: &openrtb2.App{
+							Bundle: "testBundle",
+						},
+						Site: &openrtb2.Site{
+							Page: "testPage",
+						},
+					},
+				},
+			},
+			want: map[string]string{MacroKeyBidID: "", MacroKeyAppBundle: "testBundle", MacroKeyDomain: "", MacroKeyPubDomain: "", MacroKeyPageURL: "testPage", MacroKeyAccountID: "", MacroKeyLmtTracking: "", MacroKeyConsent: "", MacroKeyBidder: "", MacroKeyIntegration: "", MacroKeyVastCRTID: "", MacroKeyAuctionID: "123", MacroKeyChannel: "", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+		{
+			name: " AppDomain, PubDomain, present key",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						App: &openrtb2.App{
+							Domain: "testDomain",
+						},
+						Site: &openrtb2.Site{
+							Publisher: &openrtb2.Publisher{
+								Domain: "pubDomain",
+							},
+						},
+					},
+				},
+			},
+			want: map[string]string{MacroKeyBidID: "", MacroKeyAppBundle: "", MacroKeyDomain: "testDomain", MacroKeyPubDomain: "pubDomain", MacroKeyPageURL: "", MacroKeyAccountID: "", MacroKeyLmtTracking: "", MacroKeyConsent: "", MacroKeyBidder: "", MacroKeyIntegration: "", MacroKeyVastCRTID: "", MacroKeyAuctionID: "", MacroKeyChannel: "", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+		{
+			name: " Integration, Consent present key",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)},
+						Ext:  []byte(`{"prebid":{"integration":"testIntegration"}}`),
+					},
+				},
+			},
+			want: map[string]string{MacroKeyBidID: "", MacroKeyAppBundle: "", MacroKeyDomain: "", MacroKeyPubDomain: "", MacroKeyPageURL: "", MacroKeyAccountID: "", MacroKeyLmtTracking: "", MacroKeyConsent: "1", MacroKeyBidder: "", MacroKeyIntegration: "testIntegration", MacroKeyVastCRTID: "", MacroKeyAuctionID: "", MacroKeyChannel: "", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+		{
+			name: " PBS-CHANNEL, LIMITADTRACKING present key",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Device: &openrtb2.Device{
+							Lmt: &lmt,
+						},
+						Ext: []byte(`{"prebid":{"channel": {"name":"test1"}}}`),
+					},
+				},
+			},
+			want: map[string]string{MacroKeyBidID: "", MacroKeyAppBundle: "", MacroKeyDomain: "", MacroKeyPubDomain: "", MacroKeyPageURL: "", MacroKeyAccountID: "", MacroKeyLmtTracking: "10", MacroKeyConsent: "", MacroKeyBidder: "", MacroKeyIntegration: "", MacroKeyVastCRTID: "", MacroKeyAuctionID: "", MacroKeyChannel: "test1", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+		{
+			name: " custom macros present key",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Ext: []byte(`{"prebid":{"macros":{"CUSTOMMACR1":"value1"}}}`),
+					},
+				},
+			},
+			want: map[string]string{"PBS-MACRO-CUSTOMMACR1": "value1", MacroKeyBidID: "", MacroKeyAppBundle: "", MacroKeyDomain: "", MacroKeyPubDomain: "", MacroKeyPageURL: "", MacroKeyAccountID: "", MacroKeyLmtTracking: "", MacroKeyConsent: "", MacroKeyBidder: "", MacroKeyIntegration: "", MacroKeyVastCRTID: "", MacroKeyAuctionID: "", MacroKeyChannel: "", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+		{
+			name: " All request macros present key",
+			args: args{
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						ID: "123",
+						Site: &openrtb2.Site{
+							Domain: "testdomain",
+							Publisher: &openrtb2.Publisher{
+								Domain: "publishertestdomain",
+								ID:     "testpublisherID",
+							},
+							Page: "pageurltest",
+						},
+						App: &openrtb2.App{
+							Domain: "testdomain",
+							Bundle: "testbundle",
+							Publisher: &openrtb2.Publisher{
+								Domain: "publishertestdomain",
+								ID:     "testpublisherID",
+							},
+						},
+						Device: &openrtb2.Device{
+							Lmt: &lmt,
+						},
+						User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)},
+						Ext:  []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`),
+					},
+				},
+			},
+			want: map[string]string{"PBS-MACRO-CUSTOMMACR1": "value1", MacroKeyBidID: "", MacroKeyAppBundle: "testbundle", MacroKeyDomain: "testdomain", MacroKeyPubDomain: "publishertestdomain", MacroKeyPageURL: "pageurltest", MacroKeyAccountID: "testpublisherID", MacroKeyLmtTracking: "10", MacroKeyConsent: "1", MacroKeyBidder: "", MacroKeyIntegration: "", MacroKeyVastCRTID: "", MacroKeyAuctionID: "123", MacroKeyChannel: "test1", MacroKeyEventType: "", MacroKeyVastEvent: ""},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			b := &macroProvider{
+				macros: map[string]string{},
+			}
+			b.populateRequestMacros(tt.args.reqWrapper)
+			output := map[string]string{}
+			for key := range tt.want {
+				output[key] = b.GetMacro(key)
+			}
+			assert.Equal(t, tt.want, output, tt.name)
+		})
+	}
+}
+
+func TestPopulateBidMacros(t *testing.T) {
+
+	type args struct {
+		bid  *entities.PbsOrtbBid
+		seat string
+	}
+	tests := []struct {
+		name      string
+		args      args
+		wantBidID string
+		wantSeat  string
+	}{
+		{
+			name: "Bid ID set, no generatedbid id, no seat",
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					Bid: &openrtb2.Bid{
+						ID: "bid123",
+					},
+				},
+			},
+			wantBidID: "bid123",
+			wantSeat:  "",
+		},
+		{
+			name: "Bid ID set, no generatedbid id, seat set",
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					Bid: &openrtb2.Bid{
+						ID: "bid123",
+					},
+				},
+				seat: "testSeat",
+			},
+			wantBidID: "bid123",
+			wantSeat:  "testSeat",
+		},
+		{
+			name: "Bid ID set, generatedbid id set, no seat",
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					GeneratedBidID: "generatedbid123",
+					Bid: &openrtb2.Bid{
+						ID: "bid123",
+					},
+				},
+			},
+			wantBidID: "generatedbid123",
+			wantSeat:  "",
+		},
+		{
+			name: "Bid ID set, generatedbid id set, seat set",
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					GeneratedBidID: "generatedbid123",
+					Bid: &openrtb2.Bid{
+						ID: "bid123",
+					},
+				},
+				seat: "testseat",
+			},
+			wantBidID: "generatedbid123",
+			wantSeat:  "testseat",
+		},
+		{
+			name: "Bid ID not set, generatedbid id set, seat set",
+			args: args{
+				seat: "test-seat",
+				bid: &entities.PbsOrtbBid{
+					GeneratedBidID: "generatedbid123",
+					Bid:            &openrtb2.Bid{},
+				},
+			},
+			wantBidID: "generatedbid123",
+			wantSeat:  "test-seat",
+		},
+		{
+			name: "Bid ID not set, generatedbid id not set, seat set",
+			args: args{
+				seat: "test-seat",
+				bid:  &entities.PbsOrtbBid{},
+			},
+			wantBidID: "",
+			wantSeat:  "test-seat",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			b := &macroProvider{
+				macros: map[string]string{},
+			}
+			b.PopulateBidMacros(tt.args.bid, tt.args.seat)
+			assert.Equal(t, tt.wantBidID, b.GetMacro(MacroKeyBidID), tt.name)
+			assert.Equal(t, tt.wantSeat, b.GetMacro(MacroKeyBidder), tt.name)
+		})
+	}
+}
+func TestPopulateEventMacros(t *testing.T) {
+
+	type args struct {
+		vastCreativeID string
+		eventType      string
+		vastEvent      string
+	}
+	tests := []struct {
+		name               string
+		args               args
+		wantVastCreativeID string
+		wantEventType      string
+		wantVastEvent      string
+	}{
+		{
+			name:               "creativeId not set, eventType not set, vastEvent not set",
+			args:               args{},
+			wantVastCreativeID: "",
+			wantEventType:      "",
+			wantVastEvent:      "",
+		},
+		{
+			name: "creativeId set, eventType not set, vastEvent not set",
+			args: args{
+				vastCreativeID: "123",
+			},
+			wantVastCreativeID: "123",
+			wantEventType:      "",
+			wantVastEvent:      "",
+		},
+		{
+			name: "creativeId not set, eventType  set, vastEvent not set",
+			args: args{
+				eventType: "win",
+			},
+			wantVastCreativeID: "",
+			wantEventType:      "win",
+			wantVastEvent:      "",
+		},
+		{
+			name: "creativeId not set, eventType not set, vastEvent set",
+			args: args{
+				vastEvent: "firstQuartile",
+			},
+			wantVastCreativeID: "",
+			wantEventType:      "",
+			wantVastEvent:      "firstQuartile",
+		},
+		{
+			name: "creativeId not set, eventType  set, vastEvent set",
+			args: args{
+				vastEvent: "firstQuartile",
+				eventType: "win",
+			},
+			wantVastCreativeID: "",
+			wantEventType:      "win",
+			wantVastEvent:      "firstQuartile",
+		},
+		{
+			name: "creativeId  set, eventType not set, vastEvent set",
+			args: args{
+				vastEvent:      "firstQuartile",
+				vastCreativeID: "123",
+			},
+			wantVastCreativeID: "123",
+			wantEventType:      "",
+			wantVastEvent:      "firstQuartile",
+		},
+		{
+			name: "creativeId set, eventType set, vastEvent not set",
+			args: args{
+				eventType:      "win",
+				vastCreativeID: "123",
+			},
+			wantVastCreativeID: "123",
+			wantEventType:      "win",
+			wantVastEvent:      "",
+		},
+		{
+			name: "creativeId set, eventType set, vastEvent set",
+			args: args{
+				vastEvent:      "firstQuartile",
+				eventType:      "win",
+				vastCreativeID: "123",
+			},
+			wantVastCreativeID: "123",
+			wantEventType:      "win",
+			wantVastEvent:      "firstQuartile",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			b := &macroProvider{
+				macros: map[string]string{},
+			}
+			b.PopulateEventMacros(tt.args.vastCreativeID, tt.args.eventType, tt.args.vastEvent)
+			assert.Equal(t, tt.wantVastCreativeID, b.GetMacro(MacroKeyVastCRTID), tt.name)
+			assert.Equal(t, tt.wantVastEvent, b.GetMacro(MacroKeyVastEvent), tt.name)
+			assert.Equal(t, tt.wantEventType, b.GetMacro(MacroKeyEventType), tt.name)
+		})
+	}
+}
+
+func TestTruncate(t *testing.T) {
+	type args struct {
+		text  string
+		width uint
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			name: "text is empty",
+			args: args{
+				text:  "",
+				width: customMacroLength,
+			},
+			want: "",
+		},
+		{
+			name: "width less than 100 chars",
+			args: args{
+				text:  "abcdef",
+				width: customMacroLength,
+			},
+			want: "abcdef",
+		},
+		{
+			name: "width exactly 100 chars",
+			args: args{
+				text:  "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv",
+				width: customMacroLength,
+			},
+			want: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv",
+		},
+		{
+			name: "width greater than 100 chars",
+			args: args{
+				text:  "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
+				width: customMacroLength,
+			},
+			want: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := truncate(tt.args.text, tt.args.width)
+			assert.Equal(t, tt.want, got, tt.name)
+		})
+	}
+}
diff --git a/macros/string_index_based_replacer_test.go b/macros/string_index_based_replacer_test.go
new file mode 100644
index 00000000..97379a6d
--- /dev/null
+++ b/macros/string_index_based_replacer_test.go
@@ -0,0 +1,146 @@
+package macros
+
+import (
+	"testing"
+
+	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/exchange/entities"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStringIndexBasedReplace(t *testing.T) {
+
+	type args struct {
+		url              string
+		getMacroProvider func() *macroProvider
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    string
+		wantErr bool
+	}{
+		{
+			name: "success",
+			args: args{
+				url: "http://tracker.com?macro1=##PBS-BIDID##&macro2=##PBS-APPBUNDLE##&macro3=##PBS-DOMAIN##&macro4=##PBS-PUBDOMAIN##&macro5=##PBS-PAGEURL##&macro6=##PBS-ACCOUNTID##&macro7=##PBS-LIMITADTRACKING##&macro8=##PBS-GDPRCONSENT##&macro9=##PBS-MACRO-CUSTOMMACR1##&macro10=##PBS-BIDDER##&macro11=##PBS-INTEGRATION##&macro12=##PBS-VASTCRTID##&macro15=##PBS-AUCTIONID##&macro16=##PBS-CHANNEL##&macro17=##PBS-EVENTTYPE##&macro18=##PBS-VASTEVENT##",
+				getMacroProvider: func() *macroProvider {
+					macroProvider := NewProvider(req)
+					macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test")
+					macroProvider.PopulateEventMacros("123", "vast", "firstQuartile")
+					return macroProvider
+				},
+			},
+			want:    "http://tracker.com?macro1=bidId123&macro2=testbundle&macro3=testdomain&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro7=10&macro8=yes&macro9=value1&macro10=test&macro11=&macro12=123&macro15=123&macro16=test1&macro17=vast&macro18=firstQuartile",
+			wantErr: false,
+		},
+		{
+			name: "url does not have macro",
+			args: args{
+				url: "http://tracker.com",
+				getMacroProvider: func() *macroProvider {
+					macroProvider := NewProvider(req)
+					macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test")
+					macroProvider.PopulateEventMacros("123", "vast", "firstQuartile")
+					return macroProvider
+				},
+			},
+			want:    "http://tracker.com",
+			wantErr: false,
+		},
+		{
+			name: "macro not found",
+			args: args{
+				url: "http://tracker.com?macro1=##PBS-test1##",
+				getMacroProvider: func() *macroProvider {
+					macroProvider := NewProvider(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}})
+					macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test")
+					macroProvider.PopulateEventMacros("123", "vast", "firstQuartile")
+					return macroProvider
+				},
+			},
+			want:    "http://tracker.com?macro1=",
+			wantErr: false,
+		},
+		{
+			name: "tracker url is empty",
+			args: args{
+				url: "",
+				getMacroProvider: func() *macroProvider {
+					macroProvider := NewProvider(&openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}})
+					macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test")
+					macroProvider.PopulateEventMacros("123", "vast", "firstQuartile")
+					return macroProvider
+				},
+			},
+			want:    "",
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			replacer := NewStringIndexBasedReplacer()
+			got, err := replacer.Replace(tt.args.url, tt.args.getMacroProvider())
+			if tt.wantErr {
+				assert.Error(t, err, tt.name)
+			} else {
+				assert.NoError(t, err, tt.name)
+				assert.Equal(t, tt.want, got, tt.name)
+			}
+		})
+	}
+}
+
+var lmt int8 = 10
+var benchmarkURL = []string{
+	"http://tracker1.com?macro1=##PBS-BIDID##&macro2=##PBS-APPBUNDLE##&macro3=##PBS-APPBUNDLE##&macro4=##PBS-PUBDOMAIN##&macro5=##PBS-PAGEURL##&macro6=##PBS-ACCOUNTID##&macro6=##PBS-LIMITADTRACKING##&macro7=##PBS-GDPRCONSENT##&macro8=##PBS-GDPRCONSENT##&macro9=##PBS-MACRO-CUSTOMMACR1CUST1##&macro10=##PBS-MACRO-CUSTOMMACR1CUST2##",
+	"http://google.com?macro1=##PBS-BIDID##&macro2=##PBS-APPBUNDLE##&macro3=##PBS-APPBUNDLE##&macro4=##PBS-PUBDOMAIN##&macro5=##PBS-PAGEURL##&macro6=##PBS-ACCOUNTID##&macro6=##PBS-LIMITADTRACKING##&macro7=##PBS-GDPRCONSENT##&macro8=##PBS-GDPRCONSENT##&macro9=##PBS-MACRO-CUSTOMMACR1CUST1##&macro10=##PBS-MACRO-CUSTOMMACR1CUST2##",
+	"http://pubmatic.com?macro1=##PBS-BIDID##&macro2=##PBS-APPBUNDLE##&macro3=##PBS-APPBUNDLE##&macro4=##PBS-PUBDOMAIN##&macro5=##PBS-PAGEURL##&macro6=##PBS-ACCOUNTID##&macro6=##PBS-LIMITADTRACKING##&macro7=##PBS-GDPRCONSENT##&macro8=##PBS-GDPRCONSENT##&macro9=##PBS-MACRO-CUSTOMMACR1CUST1##&macro10=##PBS-MACRO-CUSTOMMACR1CUST2##",
+	"http://testbidder.com?macro1=##PBS-BIDID##&macro2=##PBS-APPBUNDLE##&macro3=##PBS-APPBUNDLE##&macro4=##PBS-PUBDOMAIN##&macro5=##PBS-PAGEURL##&macro6=##PBS-ACCOUNTID##&macro6=##PBS-LIMITADTRACKING##&macro7=##PBS-GDPRCONSENT##&macro8=##PBS-GDPRCONSENT##&macro9=##PBS-MACRO-CUSTOMMACR1CUST1##&macro10=##PBS-MACRO-CUSTOMMACR1CUST2##",
+	"http://dummybidder.com?macro1=##PBS-BIDID##&macro2=##PBS-APPBUNDLE##&macro3=##PBS-APPBUNDLE##&macro4=##PBS-PUBDOMAIN##&macro5=##PBS-PAGEURL##&macro6=##PBS-ACCOUNTID##&macro6=##PBS-LIMITADTRACKING##&macro7=##PBS-GDPRCONSENT##&macro8=##PBS-GDPRCONSENT##&macro9=##PBS-MACRO-CUSTOMMACR1CUST1##&macro10=##PBS-MACRO-CUSTOMMACR1CUST2##",
+}
+
+var req *openrtb_ext.RequestWrapper = &openrtb_ext.RequestWrapper{
+	BidRequest: &openrtb2.BidRequest{
+		ID: "123",
+		Site: &openrtb2.Site{
+			Domain: "testdomain",
+			Publisher: &openrtb2.Publisher{
+				Domain: "publishertestdomain",
+				ID:     "testpublisherID",
+			},
+			Page: "pageurltest",
+		},
+		App: &openrtb2.App{
+			Domain: "testdomain",
+			Bundle: "testbundle",
+			Publisher: &openrtb2.Publisher{
+				Domain: "publishertestdomain",
+				ID:     "testpublisherID",
+			},
+		},
+		Device: &openrtb2.Device{
+			Lmt: &lmt,
+		},
+		User: &openrtb2.User{Ext: []byte(`{"consent":"yes" }`)},
+		Ext:  []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1","CUSTOMMACR2":"value2","CUSTOMMACR3":"value3"}}}`),
+	},
+}
+
+var bid *openrtb2.Bid = &openrtb2.Bid{ID: "bidId123", CID: "campaign_1", CrID: "creative_1"}
+
+func BenchmarkStringIndexBasedReplacer(b *testing.B) {
+	replacer := NewStringIndexBasedReplacer()
+	for n := 0; n < b.N; n++ {
+		for _, url := range benchmarkURL {
+			macroProvider := NewProvider(req)
+			macroProvider.PopulateBidMacros(&entities.PbsOrtbBid{Bid: bid}, "test")
+			macroProvider.PopulateEventMacros("123", "vast", "firstQuartile")
+			_, err := replacer.Replace(url, macroProvider)
+			if err != nil {
+				b.Errorf("Fail to replace macro in tracker")
+			}
+		}
+	}
+}
diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go
index 8efa3b74..ee5062ea 100644
--- a/usersync/cookie_test.go
+++ b/usersync/cookie_test.go
@@ -14,27 +14,24 @@ import (
 
 func TestOptOutCookie(t *testing.T) {
 	cookie := &Cookie{
-		uids:     map[string]uidWithExpiry{"appnexus": {UID: "test"}},
-		optOut:   true,
-		birthday: timestamp(),
+		uids:   map[string]uidWithExpiry{"appnexus": {UID: "test"}},
+		optOut: true,
 	}
 	ensureConsistency(t, cookie)
 }
 
 func TestEmptyOptOutCookie(t *testing.T) {
 	cookie := &Cookie{
-		uids:     make(map[string]uidWithExpiry),
-		optOut:   true,
-		birthday: timestamp(),
+		uids:   make(map[string]uidWithExpiry),
+		optOut: true,
 	}
 	ensureConsistency(t, cookie)
 }
 
 func TestEmptyCookie(t *testing.T) {
 	cookie := &Cookie{
-		uids:     make(map[string]uidWithExpiry),
-		optOut:   false,
-		birthday: timestamp(),
+		uids:   make(map[string]uidWithExpiry),
+		optOut: false,
 	}
 	ensureConsistency(t, cookie)
 }
@@ -68,8 +65,7 @@ func TestRejectAudienceNetworkCookie(t *testing.T) {
 		uids: map[string]uidWithExpiry{
 			"audienceNetwork": newTempId("0", 10),
 		},
-		optOut:   false,
-		birthday: timestamp(),
+		optOut: false,
 	}
 	parsed := ParseCookie(raw.ToHTTPCookie(90 * 24 * time.Hour))
 	if parsed.HasLiveSync("audienceNetwork") {
@@ -97,9 +93,8 @@ func TestOptOutReset(t *testing.T) {
 
 func TestOptIn(t *testing.T) {
 	cookie := &Cookie{
-		uids:     make(map[string]uidWithExpiry),
-		optOut:   true,
-		birthday: timestamp(),
+		uids:   make(map[string]uidWithExpiry),
+		optOut: true,
 	}
 
 	cookie.SetOptOut(false)
@@ -129,11 +124,9 @@ func TestParseCorruptedCookieJSON(t *testing.T) {
 }
 
 func TestParseNilSyncMap(t *testing.T) {
-	cookieJSON := "{\"bday\":123,\"optout\":true}"
-	cookieData := base64.URLEncoding.EncodeToString([]byte(cookieJSON))
 	raw := http.Cookie{
 		Name:  uidCookieName,
-		Value: cookieData,
+		Value: "",
 	}
 	parsed := ParseCookie(&raw)
 	ensureEmptyMap(t, parsed)
@@ -167,8 +160,7 @@ func TestParseCookieFromRequestOptOut(t *testing.T) {
 			"foo": newTempId("fooID", 1),
 			"bar": newTempId("barID", 2),
 		},
-		optOut:   false,
-		birthday: timestamp(),
+		optOut: false,
 	}).ToHTTPCookie(24 * time.Hour)
 
 	testCases := []struct {
@@ -321,8 +313,7 @@ func TestTrimCookiesClosestExpirationDates(t *testing.T) {
 			"k6": newTempId("12345678901234567890123456789012345678901234567890", 3),
 			"k7": newTempId("abcdefghijklmnopqrstuvwxyz", 2),
 		},
-		optOut:   false,
-		birthday: timestamp(),
+		optOut: false,
 	}
 
 	type aTest struct {
@@ -332,8 +323,8 @@ func TestTrimCookiesClosestExpirationDates(t *testing.T) {
 	testCases := []aTest{
 		{maxCookieSize: 2000, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}}, //1 don't trim, set
 		{maxCookieSize: 0, expKeys: []string{"k1", "k2", "k3", "k4", "k5", "k6", "k7"}},    //2 unlimited size: don't trim, set
-		{maxCookieSize: 800, expKeys: []string{"k1", "k5", "k4", "k3"}},                    //3 trim to size and set
-		{maxCookieSize: 500, expKeys: []string{"k1", "k3"}},                                //4 trim to size and set
+		{maxCookieSize: 800, expKeys: []string{"k1", "k5", "k4", "k3", "k6"}},              //3 trim to size and set
+		{maxCookieSize: 500, expKeys: []string{"k1", "k4", "k3"}},                          //4 trim to size and set
 		{maxCookieSize: 200, expKeys: []string{}},                                          //5 insufficient size, trim to zero length and set
 		{maxCookieSize: -100, expKeys: []string{}},                                         //6 invalid size, trim to zero length and set
 	}
@@ -436,8 +427,7 @@ func newSampleCookie() *Cookie {
 			"adnxs":   newTempId("123", 10),
 			"rubicon": newTempId("456", 10),
 		},
-		optOut:   false,
-		birthday: timestamp(),
+		optOut: false,
 	}
 }